Класс PyQt QThread в Python — как использовать
Если программа имеет длительные операции, она может задержаться на короткое время. В некоторых случаях программа полностью зависает.
Поэтому при разработке программ PyQt вы должны знать, как справляться с такими ситуациями. И для этого вы можете воспользоваться преимуществами потоковой обработки. Если вы не знакомы с концепцией потоковой обработки, вы можете узнать о ней больше в серии статей о параллелизме в Python.
- Знакомство с классом PyQt QThread
- Основной поток и рабочие потоки
- Пример PyQt QThread
- Определение рабочего класса
- Взаимодействие между основным и рабочим потоками
Знакомство с классом PyQt QThread
В Python имеется ряд модулей для обработки потоков, таких как threading и concurrent.futures. Вы можете использовать эти модули, но PyQt предоставляет лучший способ сделать это с помощью класса QThread и других классов.
Основной поток и рабочие потоки
Приложения Qt основаны на событиях. Когда вы вызываете метод exec(), он запускает цикл событий и создает поток, который называется основным потоком.
Любые события, происходящие в основном потоке, выполняются синхронно в основном цикле событий.
Чтобы воспользоваться преимуществами потоков, вам нужно создать вторичный поток, чтобы разгрузить длительные операции от основного потока. Вторичные потоки часто называют рабочими потоками.
Для связи между основным потоком и рабочими потоками используются сигналы и слоты. Шаги для использования класса QThread следующие:
- Сначала создайте класс, который наследует от QObject и переносит длительные операции в этот класс.
class Worker(QObject): pass
Причина, по которой мы создаем подкласс класса QObject, заключается в том, что мы хотим использовать сигнал и слот.
- Далее создаем рабочий поток и рабочий объект из основного потока.
self.worker = Worker() self.worker_thread = QThread()
Self является экземпляром QMainWindow или QWidget.
- Затем соедините сигналы и слоты класса Worker с основным потоком.
- После этого переместите обработчик в рабочий поток, вызвав метод moveToThread() объекта обработчика:
self.worker.moveToThread(self.worker_thread)
- Наконец, запустите рабочий поток:
self.worker_thread.start()
Важно отметить, что вы должны общаться с worker только через сигналы и слоты. И вы не вызываете ни один из его методов из основного потока. Например:
self.worker.do_some_work() # DON'T
Обратите внимание, что другой способ использования класса QThread — это его подкласс и переопределение метода run(). Однако это не рекомендуемый способ.
Пример PyQt QThread
Создадим простую программу, использующую QThread:
Программа состоит из полосы прогресса и кнопки. Когда вы нажимаете кнопку запуска, длительная операция запускается в рабочем потоке и обновляет прогресс обратно в основной поток через сигналы и слоты.
Вот полная программа:
import sys from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QPushButton, QVBoxLayout, QProgressBar from PyQt6.QtCore import QThread, QObject, pyqtSignal as Signal, pyqtSlot as Slot import time class Worker(QObject): progress = Signal(int) completed = Signal(int) @Slot(int) def do_work(self, n): for i in range(1, n+1): time.sleep(1) self.progress.emit(i) self.completed.emit(i) class MainWindow(QMainWindow): work_requested = Signal(int) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setGeometry(100, 100, 300, 50) self.setWindowTitle('QThread Demo') # setup widget self.widget = QWidget() layout = QVBoxLayout() self.widget.setLayout(layout) self.setCentralWidget(self.widget) self.progress_bar = QProgressBar(self) self.progress_bar.setValue(0) self.btn_start = QPushButton('Start', clicked=self.start) layout.addWidget(self.progress_bar) layout.addWidget(self.btn_start) self.worker = Worker() self.worker_thread = QThread() self.worker.progress.connect(self.update_progress) self.worker.completed.connect(self.complete) self.work_requested.connect(self.worker.do_work) # move worker to the worker thread self.worker.moveToThread(self.worker_thread) # start the thread self.worker_thread.start() # show the window self.show() def start(self): self.btn_start.setEnabled(False) n = 5 self.progress_bar.setMaximum(n) self.work_requested.emit(n) def update_progress(self, v): self.progress_bar.setValue(v) def complete(self, v): self.progress_bar.setValue(v) self.btn_start.setEnabled(True) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() sys.exit(app.exec())
Как это работает.
Определение рабочего класса
Класс Worker наследуется от класса QObject, чтобы он мог поддерживать сигналы и слоты. На практике вы перемещаете длинные операции в класс Worker:
class Worker(QObject):
Класс Worker имеет два сигнала:
- progress
- completed
Эти сигналы являются экземплярами класса pyqtSignal. Поскольку мы импортируем pyqtSignal как Signal, мы можем использовать Signal вместо него:
progress = Signal(int) completed = Signal(int)
Оба сигнала — progress и completed — принимают целое число.
Класс Worker будет выдавать сигнал о ходе выполнения, когда часть работы выполнена, и сигнал о завершении, когда работа завершена.
Класс Work имеет метод do_work():
@Slot(int) def do_work(self, n): for i in range(1, n+1): time.sleep(1) self.progress.emit(i) self.completed.emit(i)
Метод do_work() имеет декоратор @Slot()(или pyqtSlot). Декоратор @Slot() превращает метод do_work() в слот.
Декоратор @Slot() необязателен. Однако подключение сигнала к декорированному методу Python может помочь сократить использование памяти и сделать его немного быстрее.
Метод do_work() принимает целое число. Он выполняет итерацию в диапазоне от 1 до аргумента. В каждой итерации он делает паузу на одну секунду с помощью time.sleep() и выдает сигнал о ходе выполнения с текущим значением с помощью метода emit().
После завершения метод do_work() выдает сигнал завершения со значением последнего целого числа.
Взаимодействие между основным и рабочим потоками
- Сначала создадим сигнал в классе MainWindow:
work_requested = Signal(int)
- Во-вторых, создайте объект Worker и рабочий поток:
self.worker = Worker() self.worker_thread = QThread()
- В-третьих, соедините сигналы прогресса и завершения объекта-работника с методами главного окна:
self.worker.progress.connect(self.update_progress) self.worker.completed.connect(self.complete)
- В-четвертых, соедините сигнал work_requested объекта MainWindow с методом do_work объекта worker:
self.work_requested.connect(self.worker.do_work)
- В-пятых, переместите рабочий процесс в рабочий поток, вызвав метод moveToThread():
self.worker.moveToThread(self.worker_thread)
- Наконец, запустите рабочий поток:
self.worker_thread.start()