通过单击按钮调用 class 方法的不同方式

different ways of calling class method with button clicks

我正在探索如何在 PyQt 应用程序中调用 class 方法。作为测试,我创建了一个小部件来初始化一个 worker,将它放在一个单独的线程上,然后启动该线程。我还创建了两个按钮:

  1. 左边的按钮直接连接worker的run功能
  2. 右边的按钮连接到调用 worker run 函数的 widget 方法
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_())

当我单击左键时,worker 按预期在后台执行。但是,当我单击右键时,小部件会冻结,直到工作人员完成 运行。这两种方法有什么区别?

当一个信号被发射到一个函数(槽)并且该信号被发射时,Qt 检测信号发射和接收对象是否在同一个线程中。如果它们是 not,槽将在接收方的线程中执行,并且控制会立即返回到发送方。这就是允许在 Qt 上使用线程而不阻塞“主 Qt 线程”(负责保持 UI 响应)的原因。

需要理解的一个非常重要的事情是 Qt 能够检测到哪个线程发出了信号以及 slot 在哪个线程上,然后它决定是否可以直接使用 slot是否执行。

在第一种情况下,按钮(将发出信号)和 self.worker.run 在不同的线程上,Qt 知道当它尝试调用 run 时;结果是该函数将在另一个线程中执行。

在第二种情况下,Qt 只知道 make_the_worker_run,从它的角度来看,它与按钮在同一个线程中:Qt 对您在该函数中实际执行的操作一无所知。 run 在一个对象的方法中,这个方法已经被移动到另一个线程中,这并不意味着什么,结果是该函数将在主线程中执行,因此阻塞。

在有关 Threads and QObjects 的 Qt 文档中阅读有关此主题的更多信息。

对于线程、QThread、信号等的工作方式存在错误的概念。

当 QObject 移动到 QThread 时,它只告诉 Qt 事件循环,如果它调用该 QObject 的槽,则它必须在管理 QThread 的线程中执行(如果 Qt::QueuedConnection 或 Qt::AutoConnection 标志在连接中使用)。 Qt 事件循环如何调用函数?那么,为此使用信号、定时器、QMetaObject::invokedMethod、QEvents 等

上面解释了为什么第一种方法有效:通过使用点击信号调用“运行”,它通知事件循环它应该调用该方法,并且使用前面的规则它将调用线程管理 QThread。

在第二种方法中,您直接在主线程中调用它,这会阻止主线程的事件循环冻结 GUI。