When using debugpy on PyQt5 threads I observe that VSCode's Call Stack viewer shows Dummy-N thread. This is not peculiar yet. However, after effectively quitting the thread and re-running it, the old one is considered as Running (in other words it is persistent, as if some kind of hook existed). I use latest versions of VSCode (1.97.2) and Microsoft Python Debugger (2025.1.2025022401). Python 3.11.6, PyQt5 version 5.15.10. I tried changing the properties of launch.json file "justMyCode" to true as I was suggested to, but it doesn't change anything. I don't observe any memory leaks related to closing the threads (as far as rough checks of ram usage for the process are concerned). It seems like the hook is just persistent there and PyQt5 weren't notifying the debugger about finishing the thread.
main.py
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from signal_generator.signals import SignalManager
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.signalmanager = SignalManager(parent=self)
self.setWindowTitle("Python oscilloscope")
self.setWindowTitle("Checkable Button Example")
self.setGeometry(100, 100, 300, 200)
# Create a button with checkable state
self.button = QPushButton("Checkable Button", self)
self.button.setCheckable(True)
self.button.setGeometry(100, 80, 100, 40)
# Connect button signal to a slot function
self.button.clicked.connect(self.on_button_click)
self.channel1_thread = None
self.channel1_generator = None
def on_button_click(self):
if self.button.isChecked():
self.button.setText("Run background task")
self.signalmanager.start_signal_generator(1)
else:
self.button.setText("Stop background task")
self.signalmanager.stop_signal_generator(1)
def closeEvent(self, event):
self.close()
if __name__ == "__main__":
app = QApplication([])
window = Window()
# TESTING
window.show()
app.exec_()
signal_generator\signals.py
import time
from PyQt5.QtCore import QObject, pyqtSignal, QThread
import sys
import debugpy
has_trace = hasattr(sys, 'gettrace') and sys.gettrace() is not None
has_breakpoint = sys.breakpointhook.__module__ != "sys"
isdebug = has_trace or has_breakpoint
class SignalManager:
def __init__(self, parent):
self.parent = parent
def start_signal_generator(self, channel: int):
if channel == 1:
# Ensure the previous thread is properly finished
if hasattr(self, "channel1_thread") and self.channel1_thread is not None:
self.stop_signal_generator(channel)
# Define worker thread
self.channel1_thread = QThread()
self.channel1_generator = SignalGenerator(self.parent)
self.channel1_generator.moveToThread(self.channel1_thread)
self.channel1_thread.started.connect(self.channel1_generator.run)
self.channel1_generator.finished.connect(self.channel1_thread.quit)
self.channel1_generator.finished.connect(self.channel1_generator.deleteLater)
self.channel1_thread.finished.connect(self.channel1_thread.deleteLater)
self.channel1_generator.progress.connect(self._reportProgress)
self.channel1_thread.start()
def stop_signal_generator(self, channel):
if channel == 1 and self.channel1_thread is not None and self.channel1_generator.is_running():
self.channel1_generator.stop()
self.channel1_thread.quit()
self.channel1_thread.wait()
self.channel1_thread = None
def _reportProgress(self):
"""This function will be responsible for plotting updated signal."""
pass
class SignalGenerator(QObject):
finished = pyqtSignal()
progress = pyqtSignal()
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.parent = parent
self.running = True
if "noise_std_dev" in kwargs:
self.noise_std_dev = kwargs["noise_std_dev"]
def run(self):
"""Long-running task of generating/updating the signal"""
if isdebug:
debugpy.debug_this_thread()
while self.running:
if not self.running:
break
time.sleep(1)
self.progress.emit()
self.finished.emit()
self.stop()
def stop(self):
self.running = False
def is_running(self):
return self.running
launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File in Current Workspace with Qt",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/__main__.py",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"justMyCode": false
}
}
When using debugpy on PyQt5 threads I observe that VSCode's Call Stack viewer shows Dummy-N thread. This is not peculiar yet. However, after effectively quitting the thread and re-running it, the old one is considered as Running (in other words it is persistent, as if some kind of hook existed). I use latest versions of VSCode (1.97.2) and Microsoft Python Debugger (2025.1.2025022401). Python 3.11.6, PyQt5 version 5.15.10. I tried changing the properties of launch.json file
"justMyCode"totrueas I was suggested to, but it doesn't change anything. I don't observe any memory leaks related to closing the threads (as far as rough checks of ram usage for the process are concerned). It seems like the hook is just persistent there and PyQt5 weren't notifying the debugger about finishing the thread.main.py
signal_generator\signals.py
launch.json