A Minimal QThread
Subclass Example
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. What this means in practice is that only the QThread.run()
method will be executed in the new thread. All other QThread
methods are executed in the thread that created the QThread
object.
Another thing to have in mind when subclassing QThread
is that a QThread
subclass does not start its event loop unless you explicitly start one by calling QThread.exec()
in your QThread.run()
implementation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# https://doc.qt.io/qt-6/qthread.html
import sys
from PySide6.QtCore import QThread, Slot, Signal, Qt
from PySide6.QtWidgets import (QApplication, QPushButton,
QLabel, QWidget, QVBoxLayout)
# 1. Create a QThread subclass
# and override its run() method.
# Add signals as needed.
class WorkerThread(QThread):
result_ready = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
print('Init in', QThread.currentThread().objectName(),
', Loop level', QThread.currentThread().loopLevel())
def run(self):
print('Running in', QThread.currentThread().objectName(),
', Loop level', QThread.currentThread().loopLevel())
result = 'Hello World'
print(result)
self.result_ready.emit(result)
class Window(QWidget):
def __init__(self):
super().__init__()
QThread.currentThread().setObjectName('Main thread')
layout = QVBoxLayout()
self.setLayout(layout)
button = QPushButton('Start background thread')
button.clicked.connect(self.start_work_in_a_thread)
self.label = QLabel()
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(button)
layout.addWidget(self.label)
@Slot()
def start_work_in_a_thread(self):
print('Button click in', QThread.currentThread().objectName(),
', Loop level', QThread.currentThread().loopLevel())
# 2. Create the WorkerThread object
self.worker_thread = WorkerThread()
self.worker_thread.setObjectName('Worker thread')
# 3. Connect the signals with the slots
self.worker_thread.result_ready.connect(self.on_result_ready)
self.worker_thread.finished.connect(self.worker_thread.deleteLater)
# 5. Start the worker thread
self.worker_thread.start()
# 4. Handle the worker thread signals
@Slot()
def on_result_ready(self, result):
self.label.setText(result)
if __name__ == '__main__':
app = QApplication(sys.argv)
main_window = Window()
main_window.show()
sys.exit(app.exec())
There are two ways you can use QThread
to run a task in a background thread:
- Create a worker object and moving it to a background thread using
QObject.moveToThread()
and - Create a
QThread
subclass and override itsrun()
method.
The first four examples show how to use the QObject.moveToThread()
method but let’s see what a minimal example of a QThread
subclass could look like:
-
Create a
QThread
subclass (namedWorkerThread
in the example) and override itsrun()
method. Add your custom signals to theQThread
subclass and emit them fromrun()
to communicate with the main thread. -
In your main window class create a
WorkerThread
instance -
Connect the
WorkerThread
signals with the main window slots. Also connect theWorkerThread.finished
signal with theWorkerThread.deleteLater
slot to make sure the worker thread exits cleanly. -
In the main window create the slots to handle
WorkerThread
signals. In the example we set aQLabel
s text to “Hello, World” when theWorkerThread.result_ready
signal is emitted. -
Start the worker thread.
When you create a QThread
object without subclassing it (like we do in the moveToThread()
examples) the following sequence of actions occurs:
- You call
QThread.start()
from the main thread to start the background thread, - The background
QThread
emits itsstarted
signal, QThread.start()
calls theQThread.run()
method,QThread.run()
callsQThread.exec()
,QThread.exec()
enters the thread-local event loop and waits forQThread.exit()
orQThread.quit()
to be called,- You call
QThread.exit()
orQThread.quit()
to stop the background thread, - The thread-local event loop stops running and the
QThread.finished
signals is emitted, - The
QObjects
for whichQObject.deleteLater()
was called are deleted.
(Remember how in the moveToThread
examples we connected the QThread.started
signal with a worker object slot so that the slot was executed immediately on QThread.start()
; and also connected the worker finished
signal with QThread.quit()
so that the QThread
finishes as soon as the worker object slot returned)
When you subclass QThread
, the subclass does not run its local event loop unless you explicitly call QThread.exec()
in your QThread.run()
implementation. If your QThread
subclass runs without an event loop it won’t be able to use the classes that need one (like QTimer
, QTcpSocket
and QProcess
). It will be able to emit signals however.
In the example, Window.start_work_in_a_thread()
is executed in the main thread and QThread.loopLevel()
is one which means that the main thread event loop is running. WorkerThread.__init__()
is also executed in the main loop while only WorkerThread.run()
is executed in the worker thread and QThread.loopLevel()
is equal to zero which means that the worker thread event loop is not running.