Шаг 18.
Библиотека PyQt5. Знакомство с PyQt5. Многопоточные приложения. Модуль queue: создание очереди заданий (окончание)

    На этом шаге мы приведем пример использования очереди.

    Рассмотрим использование очереди в многопоточном приложении на примере.

#  -*- coding:   utf-8  -*-
from PyQt5 import QtCore, QtWidgets
import queue

class MyThread(QtCore.QThread):
    task_done = QtCore.pyqtSignal(int, int, name = 'taskDone')
    def __init__(self, id, queue, parent=None):
        QtCore.QThread.__init__(self, parent)
        self.id = id
        self.queue = queue
    def run(self):
        while True:
            task = self.queue.get()            # Получаем задание
            self.sleep(5)                      # Имитируем обработку
            self.task_done.emit(task, self.id) # Передаем данные обратно
            self.queue.task_done()

class MyWindow(QtWidgets.QPushButton):
    def __init__(self):
        QtWidgets.QPushButton.__init__(self)
        self.setText("Раздать задания")
        self.queue = queue.Queue()       # Создаем очередь
        self.threads = []
        for i in range(1, 3):	# Создаем потоки и запускаем
            thread = MyThread(i, self.queue)
            self.threads.append(thread)
            thread.task_done.connect(self.on_task_done, QtCore.Qt.QueuedConnection)
            thread.start()
        self.clicked.connect(self.on_add_task)
    def on_add_task(self):
        for i in range(0, 11):
            self.queue.put(i)	  # Добавляем задания в очередь
    def on_task_done(self, data, id):
        print(data, "- id =", id) # Выводим обработанные данные

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.setWindowTitle("Использование модуля queue")
    window.resize(300, 30)
    window.show()
    sys.exit(app.exec_())
Архив с файлом можно взять здесь.

    Результат работы приложения изображен на рисунке 1.


Рис.1. Результат работы приложения

    В этом примере конструктор класса MyThread принимает уникальный идентификатор (id) и ссылку на очередь (queue), которые сохраняются в одноименных атрибутах класса. В методе run() внутри бесконечного цикла производится получение элемента из очереди с помощью метода get(). Если очередь пуста, поток будет ожидать, пока не появится хотя бы один элемент. Далее производится обработка задания (в нашем случае - просто задержка), а затем обработанные данные передаются главному потоку через сигнал taskDone, принимающий два целочисленных параметра. В следующей инструкции с помощью метода task_done() указывается, что задание было обработано.

    Отметим, что здесь в вызове функции pyqtSignal() присутствует именованный параметр name:

  task_done = QtCore.pyqtSignal(int, int, name = 'taskDone')

    Он задает имя сигнала и может быть полезен в том случае, если это имя отличается от имени атрибута класса, соответствующего сигналу, - как в нашем случае, где имя сигнала: taskDone, отличается от имени атрибута: task_done. После чего мы можем обращаться к сигналу как по имени соответствующего ему атрибута:

  self.task_done.emit(task, self.id)
так и по имени, заданному в параметре name функции pyqtSignal():
  self.taskDone.emit(task, self.id)

    Главный поток реализуется с помощью класса MyWindow. Обратите внимание, что наследуется класс QPushButton (кнопка), а не класс QWidget. Все визуальные компоненты являются наследниками класса QWidget, поэтому любой компонент, не имеющий родителя, обладает своим собственным окном. В нашем случае используется только кнопка, поэтому можно сразу наследовать класс QPushButton.

    Внутри конструктора класса MyWindow с помощью метода setText() задается текст надписи на кнопке, затем создается экземпляр класса Queue и сохраняется в атрибуте queue. В следующем выражении производится создание списка, в котором будут храниться ссылки на объекты потоков. Сами объекты потоков (в нашем случае их два) создаются внутри цикла и добавляются в список. Внутри цикла производится также назначение обработчика сигнала taskDone и запуск потока с помощью метода start(). Далее назначается обработчик нажатия кнопки.

    При нажатии кнопки Раздать задания вызывается метод on_add_task(), внутри которого производится добавление заданий в очередь. После этого потоки выходят из цикла ожидания, и каждый из них получает одно уникальное задание. После окончания обработки потоки генерируют сигнал taskDone и вызывают метод task_done(), информирующий об окончании обработки задания. Главный поток получает сигнал и вызывает метод on_task_ done(), внутри которого через параметры будут доступны обработанные данные. Так как метод расположен в GUI-потоке, мы можем изменять свойства компонентов и, например, добавить результат в список или таблицу. В нашем же примере результат просто выводится в окно консоли (чтобы увидеть сообщения, следует сохранить файл с расширением ру, а не pyw). После окончания обработки задания потоки снова получают задания. Если очередь окажется пуста, потоки перейдут в режим ожидания заданий.

    На следующем шаге мы рассмотрим классы QMutex и QMutexLocker.




Предыдущий шаг Содержание Следующий шаг