Класс PyQt QThread в Python — как использовать

Если программа имеет длительные операции, она может задержаться на короткое время. В некоторых случаях программа полностью зависает.

Поэтому при разработке программ PyQt вы должны знать, как справляться с такими ситуациями. И для этого вы можете воспользоваться преимуществами потоковой обработки.  Если вы не знакомы с концепцией потоковой обработки, вы можете узнать о ней больше в серии статей о параллелизме в Python.

Содержание

Знакомство с классом PyQt QThread

В Python имеется ряд модулей для обработки потоков, таких как threading и concurrent.futures. Вы можете использовать эти модули, но PyQt предоставляет лучший способ сделать это с помощью класса QThread и других классов.

Основной поток и рабочие потоки

Приложения Qt основаны на событиях. Когда вы вызываете метод exec(), он запускает цикл событий и создает поток, который называется основным потоком.

Цикл событий PyQt

Любые события, происходящие в основном потоке, выполняются синхронно в основном цикле событий.

Чтобы воспользоваться преимуществами потоков, вам нужно создать вторичный поток, чтобы разгрузить длительные операции от основного потока. Вторичные потоки часто называют рабочими потоками.

Для связи между основным потоком и рабочими потоками используются сигналы и слоты. Шаги для использования класса 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:

Пример программы с PyQt 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()
Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *