使用 QThread 和 pyqtSignal,为什么不同线程中的进程会冻结 GUI?

Using QThread and pyqtSignal, why does the process which is in a different thread freeze the GUI?

我有一个应用程序,其中有一个线程正在执行一项冗长的任务。我使用发出信号的 QPushButton 触发了冗长的任务。但是,单击该按钮会使 GUI 对进程的长度无响应,即使该进程在另一个线程中也是如此。这是一个复制此行为的小程序:

import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtCore import pyqtSignal, QThread, Qt


class WriteThread(QThread):
    write_signal = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.write_signal.connect(self.worker, Qt.QueuedConnection)

    def worker(self):
        print("Before sleep")
        time.sleep(2)
        print("After sleep")
        return True


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.CustomEvent = None
        self.write_thread = None
        self.init_ui()

    def init_ui(self):
        self.write_thread = WriteThread()

        redb = QPushButton('Red', self)
        redb.move(10, 10)

        blueb = QPushButton('Blue', self)
        blueb.move(10, 50)
        blueb.clicked.connect(self.print_method)

        redb.clicked.connect(self.send_event)

        self.write_thread.start()

        self.setGeometry(300, 300, 300, 250)
        self.setWindowTitle('Toggle button')
        self.show()

    def send_event(self):
        print("\tSending signal")
        self.write_thread.write_signal.emit()
        print("\tFinished sending signal")

    def print_method(self):
        print("Not frozen")


def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

time.sleep(2)模拟冗长的过程。 我的问题确实是:为什么在不同线程中的进程会冻结 GUI?

解释:

你必须明白 QThread 不是 Qt-thread 而是原生 OS 线程的 handler,类似至 threading.Thread.

正如the docs指出的那样:

Unlike queued slots or invoked methods, methods called directly on the QThread object will execute in the thread that calls the method. When subclassing QThread, keep in mind that the constructor executes in the old thread while run() executes in the new thread. If a member variable is accessed from both functions, then the variable is accessed from two different threads. Check that it is safe to do so.

(强调我的)

也就是说,只有运行()方法在新线程上执行,所以“worker”在属于QThread的线程上执行,在本例中是主线程。

解决方案:

如果你想执行“工作”方法,那么class对象必须存在于辅助线程中,因此WriteThread是一个QObject就足够了(它不需要是QThread) ,并将其移动到另一个线程:

import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QThread, Qt, QObject


class WriteObject(QObject):
    write_signal = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.write_signal.connect(self.worker, Qt.QueuedConnection)

    @pyqtSlot()
    def worker(self):
        print("Before sleep")
        time.sleep(2)
        print("After sleep")
        return True


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.CustomEvent = None
        self.write_thread = None
        self.init_ui()

    def init_ui(self):
        self.write_object = WriteObject()
        self.write_thread = QThread(self)
        self.write_thread.start()
        self.write_object.moveToThread(self.write_thread)

        redb = QPushButton("Red", self)
        redb.move(10, 10)

        blueb = QPushButton("Blue", self)
        blueb.move(10, 50)
        blueb.clicked.connect(self.print_method)

        redb.clicked.connect(self.send_event)

        self.setGeometry(300, 300, 300, 250)
        self.setWindowTitle("Toggle button")
        self.show()

    def send_event(self):
        print("\tSending signal")
        self.write_object.write_signal.emit()
        print("\tFinished sending signal")

    def print_method(self):
        print("Not frozen")


def main():
    app = QApplication(sys.argv)
    ex = Example()
    ret = app.exec_()
    ex.write_thread.quit()
    ex.write_thread.wait()
    sys.exit(ret)


if __name__ == "__main__":
    main()