Use the Same QThread Object Multiple Times

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
92
93
94
95
96
# https://doc.qt.io/qt-6/qthread.html

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):
    
    result_ready = Signal(str)
    
    def __init__(self, parent=None):
        super().__init__(parent)
        
    @Slot()
    def do_work(self, parameter):
        print(parameter)
        self.result_ready.emit(parameter)


class Controller(QWidget):
    
    operate = Signal(str)
    
    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)
        
        # 2. Create the thread object
        
        self.worker_thread = QThread()
        
        # 3. Create the worker and move it to the thread
        
        self.worker = Worker()
        self.worker.moveToThread(self.worker_thread)
        
        # 4. Connect the signals and the slots
        
        self.worker_thread.finished.connect(
            self.worker.deleteLater)
        self.operate.connect(self.worker.do_work)
        self.worker.result_ready.connect(self.handle_results)
        
        # 5. Start the thread

        self.worker_thread.start()
    
    # 6. On the button click emit the operate signal
    
    @Slot()
    def on_button_clicked(self):
        
        self.operate.emit('Hello World')
    
    @Slot()
    def handle_results(self):
        self.label.setText('Worker finished')
    
    # 7. Quit the thread when the main window is closed
    
    def closeEvent(self, event):        
        try:
            self.worker_thread.quit()
            self.worker_thread.wait()
        except Exception as e:
            print(e) 
        event.accept()


if __name__ == '__main__':

    app = QApplication(sys.argv)

    main_window = Controller()
    main_window.show()

    sys.exit(app.exec())

In the first two examples the background/worker thread is created each time we want to execute the long running task but in the example from the QThread documentation both the thread and the worker objects are created in the main class constructor so let’s try to replicate that:

  1. Create the worker class. The method to be executed in the background is called do_work() just as in the QThread documentation. Then, in the main window class

  2. Create the worker thread,

  3. Create the Worker object and move it to the the worker thread using QObject.moveToThread(),

  4. Connect the signals with the slots. We want the worker object deleted when the thread finishes so we connect QThread.finished with QObject.deleteLater. We want to send a custom signal (operate) to the worker object to start the work so we connect Controller.operate with Worker.do_work. Finally, we connect Worker.result_ready with Controller.handle_result so we can handle the do_work result in the main class.

  5. Start the worker thread. The steps 2 through 5 are executed in the Controller.__init__() method which means both the thread and the worker objects will exist until the main window is closed or we delete them explicitly.

  6. On the button click emit the operate signal. This executes the Worker.do_work() method.

  7. Override QWidget.closeEvent() to quit the thread using the QThread.quit() and QThread.wait() pair.