29. Multithreading with QThreadPool and QRunnable
The QThreadPool class manages a collection of QThreads and is used with the QRunnable class.
29.1 A Minimal Example
![]() |
You need to provide a working multithreading application using |
1 from PySide6.QtCore import QObject, QRunnable, Signal
2
3
4 class Signals(QObject):
5 progress = Signal(str)
6 error = Signal(str)
7
8 # 1. Create a QRunnable subclass
9 # and implement its run() method.
10
11 class Runnable(QRunnable):
12
13 def __init__(self, parent=None):
14 super().__init__(parent)
15 self.signals = Signals()
16
17 # The run method will be executed
18 # in the worker thread.
19
20 def run(self):
21 self.signals.progress.emit('Progress emitted')
22 print('Hello World')
23 self.signals.deleteLater()
- Create a
QRunnablesubclass and implement itsrun()method with the code to execute in a background thread.QRunnableis not aQObjectsubclass, so it cannot have signals directly. You can easily work around this by creating a separateQObjectsubclass (namedSignalshere) and adding an instance to yourQRunnable. In this example,run()emitsSignals.progressand prints a message.
1 import sys
2
3 from PySide6.QtCore import QThreadPool, Slot, Qt
4 from PySide6.QtWidgets import (QApplication,
5 QPushButton, QLabel, QWidget, QVBoxLayout)
6 from runnable import Runnable
7
8
9 class Window(QWidget):
10
11 def __init__(self):
12
13 super().__init__()
14
15 layout = QVBoxLayout()
16 self.setLayout(layout)
17
18 button = QPushButton('Start background thread')
19 button.clicked.connect(self.on_button_clicked)
20
21 self.label = QLabel()
22 self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
23
24 layout.addWidget(button)
25 layout.addWidget(self.label)
26
27 # When the button is clicked:
28
29 @Slot()
30 def on_button_clicked(self):
31
32 # 2. Create a Runnable object
33
34 runnable = Runnable()
35
36 runnable.signals.progress.connect(self.label.setText)
37 runnable.signals.error.connect(self.on_error)
38
39 # 3. Access the QThreadPool global instance
40 # and run the task.
41
42 QThreadPool.globalInstance().start(runnable)
43
44 @Slot()
45 def on_error(self, message):
46 print(message)
47
48
49 if __name__ == '__main__':
50
51 app = QApplication(sys.argv)
52
53 main_window = Window()
54 main_window.show()
55
56 sys.exit(app.exec())
In the main window, create a
Runnableinstance.Use
QThreadPool.globalInstance()to access the global thread pool andQThreadPool.start()to executeRunnable.run()in one of its threads.
29.2 Walking the Filesystem
![]() |
Your task is to walk the filesystem using |
1 import os
2 from PySide6.QtCore import (QObject, QThread,
3 QRunnable, Signal, Slot)
4
5
6 class Signals(QObject):
7 progress = Signal(str)
8 error = Signal(str)
9
10 # 1. Create a QRunnable subclass
11 # and implement its run() method
12
13 class Runnable(QRunnable):
14
15 def __init__(self, parent=None):
16 super().__init__(parent)
17 self.signals = Signals()
18 self.do_work = True
19
20 # Enumerate fs objects while self.do_work flag is True
21
22 def run(self):
23 path = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
24 for root, _, _ in os.walk(path):
25 if not self.do_work:
26 return
27 self.signals.progress.emit(os.path.basename(root))
28 print(QThread.currentThread())
29
30 @Slot()
31 def on_cancel_emitted(self):
32 self.do_work = False
- Create a
QRunnablesubclass and implementrun(). Add a member for signals and a boolean flagdo_workfor interruption.run()usesos.walk()to enumerate filesystem objects, emittingprogress()for each whiledo_workisTrue.
1 import sys
2 from PySide6.QtCore import QThreadPool, Slot, Signal, Qt
3 from PySide6.QtWidgets import (QApplication,
4 QPushButton, QLabel, QWidget, QVBoxLayout)
5 from runnable import Runnable
6
7
8 class Window(QWidget):
9
10 # 2. add a custom signal to be emitted
11 # when we want to cancel the task.
12
13 cancel_runnable = Signal()
14
15 def __init__(self):
16
17 super().__init__()
18
19 layout = QVBoxLayout()
20 self.setLayout(layout)
21
22 self.start_button = QPushButton('Start background thread')
23 self.start_button.clicked.connect(self.on_start_button_clicked)
24
25 self.cancel_button = QPushButton('Cancel')
26 self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
27 self.cancel_button.setDisabled(True)
28
29 self.label = QLabel()
30 self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
31
32 layout.addWidget(self.start_button)
33 layout.addWidget(self.cancel_button)
34 layout.addWidget(self.label)
35
36 @Slot()
37 def on_start_button_clicked(self):
38
39 # 3. Create a Runnable object
40 # and connect the signals and the slots
41
42 runnable = Runnable()
43
44 runnable.signals.progress.connect(self.label.setText)
45 runnable.signals.error.connect(self.on_error)
46 self.cancel_runnable.connect(runnable.on_cancel_emitted)
47
48 # 4. Run the task.
49
50 QThreadPool.globalInstance().start(runnable)
51
52 self.start_button.setDisabled(True)
53 self.cancel_button.setEnabled(True)
54
55 @Slot()
56 def on_cancel_button_clicked(self):
57 self.cancel_runnable.emit()
58 self.start_button.setEnabled(True)
59 self.cancel_button.setDisabled(True)
60
61 @Slot()
62 def on_error(self, message):
63 print(message)
64
65 # Emit the cancel_runnable signal to interrupt
66 # the runnable on the main window close
67
68 def closeEvent(self, event):
69 try:
70 self.cancel_runnable.emit()
71 except Exception as e:
72 print(e)
73
74
75 if __name__ == '__main__':
76
77 app = QApplication(sys.argv)
78
79 main_window = Window()
80 main_window.show()
81
82 sys.exit(app.exec())
In the main window class, add a custom
cancel_runnable()signal. Connect it toRunnable.on_cancel_emitted(), which setsRunnable.do_worktoFalse.In
on_start_button_clicked()create aRunnableobject and connects signals to slots:progressupdates the label; on cancel, emitcancel_runnable()to setdo_workFalse.Access the global thread pool and run the task with
QThreadPool.start().
