29. 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):
14 super().__init__()
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 main_window = Window()
53 main_window.show()
54 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, QMutex, QMutexLocker, Signal, Slot)
4
5
6 class Signals(QObject):
7 progress = Signal(str)
8 error = Signal(str)
9
10
11 # 1. Create a QRunnable subclass
12 # and implement its run() method
13
14 class Runnable(QRunnable):
15
16 def __init__(self):
17 super().__init__()
18 self.signals = Signals()
19 self.mutex = QMutex()
20 self.do_work = True
21
22 def is_working(self):
23 with QMutexLocker(self.mutex):
24 return self.do_work
25
26 def stop(self):
27 with QMutexLocker(self.mutex):
28 self.do_work = False
29
30 # Enumerate fs objects while self.do_work flag is True
31
32 def run(self):
33 path = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
34 for root, _, _ in os.walk(path):
35 if not self.is_working():
36 return
37 self.signals.progress.emit(os.path.basename(root))
38
39 @Slot()
40 def on_cancel_emitted(self):
41 self.stop()
- Create a
QRunnablesubclass and implementrun(). Add a member for signals and a boolean flag,do_work, for interruption, guarded by aQMutexsince it’s written from the main thread and read from the pool worker thread.is_working()andstop()wrap the guarded reads and writes in aQMutexLocker.run()usesos.walk()to enumerate filesystem objects, emittingprogress()for each whileis_working()isTrue.
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 main_window = Window()
79 main_window.show()
80 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().
In the last three chapters we worked hard to pull pieces of code out of sync running them concurrently with moveToThread(), custom QThread subclasses, and the QThreadPool/QRunnable combination. In the next chapter we will work hard to bring that code back into sync again.
