28. Using a QThread subclass
Perhaps the most important thing to remember about QThread is that a QThread object does not represent an operating system thread - it is a thread manager. In practice, this means only the QThread.run() method executes in the new thread. All other QThread methods run in the thread that created the QThread object.
Another point when subclassing QThread is that a QThread subclass does not start its event loop unless you explicitly call QThread.exec() in your QThread.run() implementation.
28.1 A Minimal Example
There are two ways to use QThread to run a background task:
- Create a worker object and move it to a background thread using
QObject.moveToThread(). - Create a
QThreadsubclass and override itsrun()method.
The examples from the previous chapter used QObject.moveToThread(). Now let’s examine a minimal QThread subclass.
![]() |
You need to provide a working multithreading application by subclassing |
1 from PySide6.QtCore import QThread, Signal, Slot
2
3 # 1. Create a QThread subclass
4 # and override its run() method.
5 # Add signals as needed.
6
7 class WorkerThread(QThread):
8
9 result_ready = Signal(str)
10
11 def __init__(self, parent=None):
12 super().__init__(parent)
13 print('Init in', QThread.currentThread().objectName(),
14 ', Loop level', QThread.currentThread().loopLevel())
15
16 def run(self):
17
18 print('Running in', QThread.currentThread().objectName(),
19 ', Loop level', QThread.currentThread().loopLevel())
20
21 result = 'Hello World'
22 print(result)
23 self.result_ready.emit(result)
- Create a
QThreadsubclass (namedWorkerThreadin the example) and override itsrun()method. Add custom signals to the subclass and emit them fromrun()to communicate with the main thread.
1 # https://doc.qt.io/qt-6/qthread.html
2
3 import sys
4
5 from PySide6.QtCore import QThread, Slot, Qt
6 from PySide6.QtWidgets import (QApplication, QPushButton,
7 QLabel, QWidget, QVBoxLayout)
8 from worker_thread import WorkerThread
9
10
11 class Window(QWidget):
12
13 def __init__(self):
14
15 super().__init__()
16
17 QThread.currentThread().setObjectName('Main thread')
18
19 layout = QVBoxLayout()
20 self.setLayout(layout)
21
22 button = QPushButton('Start background thread')
23 button.clicked.connect(self.start_work_in_a_thread)
24
25 self.label = QLabel()
26 self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
27
28 layout.addWidget(button)
29 layout.addWidget(self.label)
30
31 @Slot()
32 def start_work_in_a_thread(self):
33
34 print('Button click in', QThread.currentThread().objectName(),
35 ', Loop level', QThread.currentThread().loopLevel())
36
37 # 2. Create the WorkerThread object
38
39 self.worker_thread = WorkerThread()
40 self.worker_thread.setObjectName('Worker thread')
41
42 # 3. Connect the signals with the slots
43
44 self.worker_thread.result_ready.connect(self.on_result_ready)
45 self.worker_thread.finished.connect(self.worker_thread.deleteLater)
46
47 # 5. Start the worker thread
48
49 self.worker_thread.start()
50
51 # 4. Handle the worker thread signals
52
53 @Slot()
54 def on_result_ready(self, result):
55 self.label.setText(result)
56
57
58 if __name__ == '__main__':
59
60 app = QApplication(sys.argv)
61 main_window = Window()
62 main_window.show()
63
64 sys.exit(app.exec())
In your main window class, create a
WorkerThreadinstance.Connect the
WorkerThreadsignals to main window slots. Also connectWorkerThread.finished()toWorkerThread.deleteLater()for clean thread exit.Create slots in the main window to handle
WorkerThreadsignals. Here, we set aQLabels text to “Hello, World” onWorkerThread.result_ready().Start the worker thread.
When creating a plain QThread object (as in moveToThread() examples) the sequence is:
- Call
QThread.start()from the main thread. - The background
QThreademitsstarted(). QThread.start()invokesQThread.run().QThread.run()callsQThread.exec().QThread.exec()enters the thread-local event loop, waiting forQThread.exit()orQThread.quit().- Call
QThread.exit()orQThread.quit()to stop. - The event loop stops, emitting
QThread.finished() QObjectswithQObject.deleteLater()are deleted.
(Remember that in moveToThread() examples, we connected QThread.started() to a worker slot for immediate execution; and worker finished() to QThread.quit() for thread completion.)
When subclassing QThread, no local event loop unless QThread.exec() is called in run(). Without it, classes that need an event loop (e.g., QTimer, QTcpSocket, QProcess) won’t work. However, the thread object will still be able to emit signals.
In this example, Window.start_work_in_a_thread() runs in the main thread (QThread.loopLevel() = 1, event loop active). WorkerThread.__init__() also runs in the main thread, while only WorkerThread.run() executes in the worker thread (QThread.loopLevel() = 0, no event loop).
28.2 Walking the Filesystem
![]() |
Now let’s walk the filesystem using a |
1 import os
2 from PySide6.QtCore import QThread, Signal
3
4 # 1. Create a QThread subclass
5 # and subclass its run() method.
6 # Add signals as needed.
7
8 class WorkerThread(QThread):
9
10 progress = Signal(str)
11
12 def __init__(self, parent=None):
13 super().__init__(parent)
14 print('Init it', QThread.currentThread().objectName())
15
16 def run(self):
17
18 print('Running in: ',
19 QThread.currentThread().objectName())
20 print('event loop level: ',
21 QThread.currentThread().loopLevel())
22
23 path = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
24 for root, _, _ in os.walk(path):
25 if QThread.currentThread().isInterruptionRequested():
26 return
27 self.progress.emit(os.path.basename(root))
- Create a
QThreadsubclass namedWorkerThreadand overriderun(). Inrun(), print the current thread object name to confirm execution in the worker thread, then useos.walk()for recursive traversal. Emitprogress()with each object’s name as the argument.
1 import sys
2 from PySide6.QtCore import QThread, Slot, Qt
3 from PySide6.QtWidgets import (QApplication, QPushButton,
4 QLabel, QWidget, QVBoxLayout)
5 from worker_thread import WorkerThread
6
7
8 class Window(QWidget):
9
10 def __init__(self):
11
12 super().__init__()
13
14 QThread.currentThread().setObjectName('Main thread')
15
16 layout = QVBoxLayout()
17 self.setLayout(layout)
18
19 self.start_button = QPushButton('Start background thread')
20 self.start_button.clicked.connect(self.on_start_button_clicked)
21
22 self.cancel_button = QPushButton('Cancel')
23 self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
24 self.cancel_button.setDisabled(True)
25
26 self.label = QLabel()
27 self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
28
29 layout.addWidget(self.start_button)
30 layout.addWidget(self.cancel_button)
31 layout.addWidget(self.label)
32
33 @Slot()
34 def on_start_button_clicked(self):
35
36 print('Main thread loop level',
37 QThread.currentThread().loopLevel())
38
39 # 2. Create the WorkerThread object
40
41 self.worker_thread = WorkerThread()
42 self.worker_thread.setObjectName('Worker thread')
43
44 # 3. Connect the signals with the slots
45
46 self.worker_thread.progress.connect(self.on_progress)
47 self.worker_thread.finished.connect(
48 self.worker_thread.deleteLater)
49
50 self.start_button.setDisabled(True)
51 self.cancel_button.setEnabled(True)
52
53 # 5. Start the worker thread
54
55 self.worker_thread.start()
56
57 # 4. Handle the signals
58
59 @Slot()
60 def on_cancel_button_clicked(self):
61
62 self.start_button.setEnabled(True)
63 self.cancel_button.setDisabled(True)
64
65 if hasattr(self, 'worker_thread'):
66 self.worker_thread.requestInterruption()
67 self.worker_thread.wait()
68
69 @Slot()
70 def on_progress(self, msg):
71 self.label.setText(msg)
72
73 # Make sure the thread is destroyed
74 # when the main window is closed.
75
76 def closeEvent(self, event):
77 try:
78 self.worker_thread.requestInterruption()
79 self.worker_thread.wait()
80 except Exception as e:
81 print(e)
82
83
84 if __name__ == '__main__':
85
86 app = QApplication(sys.argv)
87 main_window = Window()
88 main_window.show()
89
90 sys.exit(app.exec())
On start button click, create a
WorkerThreadobject and set its name to “Worker thread”.Connect signals to slots:
WorkerThread.progress()toWindow.on_progress()for label updates;WorkerThread.finished()toWorkerThread.deleteLater()for cleanup.Handle signals. On
progress()update the label; On cancel request interruption withQThread.requestInterruption()and wait for finish.Start the worker thread on each start button click.
Override Qwidget.closeEvent() to ensure thread deletion on window close. Note that we didn’t need to call QThread.quit() but only QThread.wait() since no event loop runs.
