На этом шаге мы рассмотрим пример создания потока.
Рассмотрим использование класса QThread на примере.
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyThread(QtCore.QThread): mysignal = QtCore.pyqtSignal(str) def __init__(self, parent=None): QtCore.QThread.__init__(self, parent) def run(self): for i in range(1, 21): self.sleep(3) # "Засыпаем" на 3 секунды # Передача данных из потока через сигнал self.mysignal.emit("i = %s" % i) class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.label = QtWidgets.QLabel("Нажмите кнопку для запуска потока") self.label.setAlignment(QtCore.Qt.AlignHCenter) self.button = QtWidgets.QPushButton("Запустить процесс") self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.label) self.vbox.addWidget(self.button) self.setLayout(self.vbox) self.mythread = MyThread() # Создаем экземпляр класса self.button.clicked.connect(self.on_clicked) self.mythread.started.connect(self.on_started) self.mythread.finished.connect(self.on_finished) self.mythread.mysignal.connect(self.on_change, QtCore.Qt.QueuedConnection) def on_clicked(self): self.button.setDisabled(True) # Делаем кнопку неактивной self.mythread.start() # Запускаем поток def on_started(self): # Вызывается при запуске потока self.label.setText("Вызван метод on_started ()") def on_finished(self): # Вызывается при завершении потока self.label.setText("Вызван метод on_finished()") self.button.setDisabled(False) # Делаем кнопку активной def on_change(self, s): self.label.setText(s) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Использование класса QThread") window.resize(300, 70) window.show() sys.exit(app.exec_())
Здесь мы создали класс MyThread, который является наследником класса QThread. В нем мы определили свой собственный сигнал mysignal, для чего создали атрибут с таким же именем и занесли в него значение, возвращенное функцией pyqtSignal() из модуля QtCore. Функции pyqtSignal() мы передали в качестве параметра тип str (строка Python), тем самым указав PyQt, что вновь определенный сигнал будет принимать единственный параметр строкового типа:
mysignal = QtCore.pyqtSignal(str)
В том же классе мы определили обязательный для потоков метод run() - в нем производится имитация процесса с помощью цикла for и метода sleep(): каждые три секунды выполняется генерация сигнала mysignal и передача текущего значения переменной i в составе строки:
self.mysignal.emit("i = %s" % i)
Внутри конструктора класса MyWindow мы назначили обработчик этого сигнала с помощью выражения:
self.mythread.mysignal.connect(self.on_change, QtCore.Qt.QueuedConnection)
Здесь все нам уже знакомо: у свойства mysignal потока, которое представляет одноименный сигнал, вызывается метод connect(), и ему первым параметром передается обработчик. Во втором параметре метода connect() с помощью атрибута QueuedConnection указывается, что сигнал помещается в очередь обработки событий, и обработчик должен выполняться в потоке приемника сигнала, т. е. в GUI-потоке. Из GUI-потока мы можем смело изменять свойства компонентов интерфейса.
Теперь рассмотрим код метода класса MyWindow, который станет обработчиком сигнала mysignal:
def on_change(self, s): self.label.setText(s)
Второй параметр этого метода служит для приема параметра, переданного этому сигналу. Значение этого параметра будет выведено в надписи с помощью метода setText().
Еще внутри конструктора класса MyWindow производится создание надписи и кнопки, а затем их размещение внутри вертикального контейнера. Далее выполняется создание экземпляра класса MyThread и сохранение его в атрибуте mythread. С помощью этого атрибута мы можем управлять потоком и назначить обработчики сигналов started(), finished() и mysignal. Запуск потока производится с помощью метода start() внутри обработчика нажатия кнопки. Чтобы исключить повторный запуск потока, мы с помощью метода setDisabled() делаем кнопку неактивной, а после окончания работы потока внутри обработчика сигнала finished() опять делаем кнопку активной.
Обратите внимание, что для имитации длительного процесса мы использовали статический метод sleep() из класса QThread, а не функцию sleep() из модуля time. Вообще, приостановить выполнение потока позволяют следующие статические методы класса QThread:
QtCore.QThread.sleep(3) # "Засыпаем" на 3 секунды
QtCore.QThread.msleep(3000) # "Засыпаем" на 3 секунды
QtCore.QThread.usleep(3000000) # "Засыпаем" на 3 секунды
Еще один полезный статичный метод класса QThread - yieldCurrentThread() - немедленно приостанавливает выполнение текущего потока и передает управление следующему ожидающему выполнения потоку, если таковой есть. Пример:
QtCore.QThread.yieldCurrentThread()
На следующем шаге мы рассмотрим управление циклом внутри потока.