QObject.moveToThread() Template

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
89
90
91
# https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

import sys

from PySide6.QtCore import (QObject, QThread,
    Slot, Signal, Qt)
from PySide6.QtWidgets import (QApplication,
    QPushButton, QLabel, QWidget, QVBoxLayout)


# 1. Create the worker class

class Worker(QObject):
    
    finished = Signal()
    error = Signal(str)
    
    def __init__(self, parent=None):
        super().__init__(parent)
    
    # This method to be executed
        
    @Slot()
    def process(self):
        print('Hello World')
        self.finished.emit()


class Window(QWidget):
    
    def __init__(self):

        super().__init__()
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        
        button = QPushButton('Start background thread')
        button.clicked.connect(self.on_button_clicked)
        
        self.label = QLabel()
        self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        
        layout.addWidget(button)
        layout.addWidget(self.label)
    
    @Slot()
    def on_button_clicked(self):
        
        # 2. Create the thread object
        
        self.background_thread = QThread()
        
        # 3. Create the worker and move it to the thread
        
        self.worker = Worker()
        self.worker.moveToThread(self.background_thread)
        
        self.worker.finished.connect(self.on_finished)
        
        # 4. Connect the signals and the slots 
        
        self.worker.error.connect(self.on_error)
        self.background_thread.started.connect(self.worker.process)
        self.worker.finished.connect(self.background_thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.background_thread.finished.connect(self.background_thread.deleteLater)
        
        # 5. Start the thread
        
        self.background_thread.start()
    
    @Slot()
    def on_finished(self):
        self.label.setText('Worker finished')
    
    @Slot()
    def on_error(self, message):
        print(message)


if __name__ == '__main__':

    app = QApplication(sys.argv)

    main_window = Window()
    main_window.show()

    sys.exit(app.exec())


In the previous example we demonstrated how a PySide6 Gui can become nonresponsive. Now let’s see how we can use Qt threads to execute long running tasks in the background while keeping the Gui responsive

  1. Create a QObject subclass that contains a slot / method to be executed in a background thread (ie a thread other than the Gui thread). In the example, the slot to be executed is called process() and simply prints a message and emits a custom signal named finished before it returns. This is how you communicate between Qt threads - using signals and slots. In addition to finished the Worker class declares another signal named error which we never call but would be emitted on error conditions in the process() execution. Then, in the main window class

  2. Create a QThread object named background_thread or something similar (don’t call it thread as that name is already taken). Note how we use self to make background_thread the main window member - this makes sure background_thread doesn’t go out of scope while the thread is still running.

  3. Create a Worker object and use QObject.moveToThread() to move it to background_thread. Now the Worker.process() method will be executed in background_thread.

  4. Connect the appropriate signals and slots:
    • connect the background_thread.started signal with the worker.process() slot. This means that Worker.process() will be executed as soon as the background thread is started,
    • connect the Worker.finished signal with the background_thread.quit method. This means that the background thread will quit as soon as the Worker.process() method returns,
    • connect the Worker.finished signal with the Worker.deleteLater() method. This means that the worker object will be deleted some time after it emits the finished signal.
    • connect the background_thread.finished signal with the background_thread.deleteLater() method. This means that the background thread will be deleted some time after it finishes.
  5. Finally, start the background_thread using the QThread.start() method.

With the above setup the process() method is executed as soon as the thread is started, the thread quits as soon as the method returns and both the worker object and the thread object are destroyed while the Gui stays responsive.