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 its run() 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:

  1. Create a QThread subclass (named WorkerThread in the example) and override its run() method. Add your custom signals to the QThread subclass and emit them from run() to communicate with the main thread.

  2. In your main window class create a WorkerThread instance

  3. Connect the WorkerThread signals with the main window slots. Also connect the WorkerThread.finished signal with the WorkerThread.deleteLater slot to make sure the worker thread exits cleanly.

  4. In the main window create the slots to handle WorkerThread signals. In the example we set a QLabels text to “Hello, World” when the WorkerThread.result_ready signal is emitted.

  5. 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 its started signal,
  • QThread.start() calls the QThread.run() method,
  • QThread.run() calls QThread.exec(),
  • QThread.exec() enters the thread-local event loop and waits for QThread.exit() or QThread.quit() to be called,
  • You call QThread.exit() or QThread.quit() to stop the background thread,
  • The thread-local event loop stops running and the QThread.finished signals is emitted,
  • The QObjects for which QObject.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.