#python #multithreading #pyqt #qthread
#python #многопоточность #pyqt #qthread
Вопрос:
Я изучаю, как вызывать методы класса в приложениях PyQt. В качестве теста я создал виджет, который инициализирует worker, помещает его в отдельный поток и запускает поток. Я также создал две кнопки:
- Левая кнопка напрямую подключается к
run
функции рабочего - Правая кнопка подключается к методу виджета, который вызывает рабочую
run
функцию
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
from time import sleep
class Worker(QObject):
worker_finished = pyqtSignal()
def __init__(self):
super().__init__()
@pyqtSlot()
def run(self):
print("Sleeping")
for ii in range(5):
print(ii)
sleep(1)
print("Finished sleeping")
class MainApp(QWidget):
def __init__(self):
super().__init__()
self._threads = []
#Create the worker
self.worker = Worker()
layout = QHBoxLayout(self)
#Move the worker on the thread and start
self.put_on_thread_and_start(self.worker)
#This button calls worker.run() directly
button = QPushButton("Call ""run"" method directly")
button.clicked.connect(self.worker.run)
layout.addWidget(button)
#This button calls a widget method, which calls worker.run()
button = QPushButton("Call ""run"" method through this widget class method")
button.clicked.connect(self.make_the_worker_run)
layout.addWidget(button)
def put_on_thread_and_start(self, worker_class):
myThread = QThread()
self._threads.append(myThread)
worker_class.moveToThread(myThread)
print("Starting thread...")
myThread.start()
def make_the_worker_run(self):
self.worker.run()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
Когда я нажимаю левую кнопку, рабочий выполняется в фоновом режиме, как и ожидалось. Однако, когда я нажимаю правую кнопку, виджет зависает до тех пор, пока рабочий не завершит работу. В чем разница между двумя подходами?
Ответ №1:
Когда сигнал передается в функцию (слот) и этот сигнал передается, Qt определяет, находятся ли излучение сигнала и принимающие объекты в одном потоке. Если это не так, слот выполняется в потоке получателя, и вместо этого управление немедленно возвращается отправителю. Это то, что позволяет использовать потоки на Qt, не блокируя «основной поток Qt» (который отвечает за адаптивность пользовательского интерфейса).
Очень важно понимать, что Qt способен определять, какой поток отправил сигнал и в каком потоке находится слот, затем он решает, может ли слот быть выполнен напрямую или нет.
В вашем первом случае кнопка (которая будет выдавать сигнал) и кнопка self.worker.run
находятся в разных потоках, и Qt знает это, когда пытается вызвать run
; в результате функция будет выполнена в другом потоке.
Во втором случае Qt знает только о make_the_worker_run
том, что, с его точки зрения, находится в том же потоке кнопки: Qt ничего не знает о том, что вы на самом деле делаете в этой функции. Тот факт, что run
в методе объекта, который был перемещен в другой поток, ничего не значит, и результатом является то, что функция будет выполняться в основном потоке, отсюда и блокировка.
Подробнее об этой теме читайте в документах Qt о потоках и QObjects.
Ответ №2:
Существует неправильное представление о том, как потоки, QThread, сигналы и т. Д. работа.
Когда QObject перемещается в QThread, он только сообщает циклу событий Qt, что если он вызывает слот этого QObject, он должен быть выполнен в потоке, который управляет QThread (если в соединении используется флаг Qt ::QueuedConnection или Qt::AutoConnection). И как Qt eventloop вызывает функции? Ну, для этого используйте сигналы, таймеры, QMetaObject::invokedMethod, QEvents и т. Д.
Вышеизложенное объясняет, почему работает первый метод: используя сигнал clicked для вызова «run», он уведомляет eventloop о том, что он должен вызвать метод, и, используя предыдущее правило, он вызовет поток, который управляет QThread.
Во втором методе вместо этого вы вызываете его непосредственно в основном потоке, который блокирует цикл событий основного потока, замораживающий графический интерфейс.