如何在 PyQt 中使用双向 QThreads 设置信号和插槽?

How do I setup Signals and Slots in PyQt with QThreads in both directions?

这是基于 ekhumoro 回答的后续问题 and here


我想明白了,当一个插槽被正确定义为 pyqtSlot 并分配给 QThread(例如 moveToThread())时,它将在这个 QThread 中执行,而不是打电话的那个此外,还需要与 Qt.QueuedConnectionQt.AutoConnection 建立联系。

我写代码来测试这个。我的目标是实现一些非常简单的事情:
带有按钮的 Gui,开始一些耗时的工作,return 结果显示在 GUI 中。

from PyQt5.Qt import *

class MainWindow(QMainWindow):
    change_text = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.button = QPushButton('Push me!', self)
        self.setCentralWidget(self.button)

        print('main running in:', QThread.currentThread())
        thread = Thread(change_text, self)
        thread.start()
        self.button.clicked.connect( thread.do_something_slow, Qt.QueuedConnection)
        self.change_text.connect(self.display_changes, Qt.QueuedConnection)

    @pyqtSlot(str)
    def display_changes( self, text ):
        self.button.setText(text)

class Thread(QThread):
    def __init__(self, signal_to_emit, parent):
        super().__init__(parent)
        self.signal_to_emit = signal_to_emit
        #self.moveToThread(self) #doesn't help

    @pyqtSlot()
    def do_something_slow( self ):
        print('Slot doing stuff in:', QThread.currentThread())
        import time
        time.sleep(5)
        self.signal_to_emit.emit('I did something')

if __name__ == '__main__':
    app = QApplication([])
    main = MainWindow()
    main.show()
    app.exec()

但是 ..gui 正在阻塞并且插槽在主线程中被调用。
我错过了什么?一定要小(我希望)。

问题是你混淆了QThread是一个Qt线程,也就是Qt创建的新线程,但是不是,QThread 处理 本机线程的 class,只有 run() 方法在另一个线程上 运行ning,其他方法位于线程中QThread 条命,也就是 QObject,

QObject 存在于哪个线程中?

QObject 所在的线程是父线程,如果它没有父线程,它将是创建它的线程。另一方面,QObject 可以使用 moveToThread() 移动到另一个线程,它的所有子线程也将移动。如果 QObject 没有父级,则只能使用 moveToThread(),否则会失败。


使用 QThread 的方法是创建一个继承自 QThread 的 class 并覆盖 运行 方法并调用 start() 以便它starts 运行ning run(),在run()方法中会完成繁重的任务,但在你的情况下不能使用这种形式,因为任务不会连续执行。比繁重的任务更好的选择是 QObject 的一部分,然后 QObject 将其移动到另一个线程。

class MainWindow(QMainWindow):
    change_text = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.button = QPushButton('Push me!', self)
        self.setCentralWidget(self.button)
        print('main running in:', QThread.currentThread())
        # A Worker without a parent is created 
        # so that it can be moved to another thread.
        self.worker = Worker(self.change_text)
        thread = QThread(self) 
        self.worker.moveToThread(thread)
        thread.start()
        # All methods of self.worker are now executed in another thread.
        self.button.clicked.connect(self.worker.do_something_slow)
        self.change_text.connect(self.display_changes)

    @pyqtSlot(str)
    def display_changes( self, text ):
        self.button.setText(text)


class Worker(QObject):
    def __init__(self, signal_to_emit, parent=None):
        super().__init__(parent)
        self.signal_to_emit = signal_to_emit

    @pyqtSlot()
    def do_something_slow( self ):
        print('Slot doing stuff in:', QThread.currentThread())
        import time
        time.sleep(5)
        self.signal_to_emit.emit('I did something')

另一方面,没有必要指明连接类型,因为默认情况下它是 Qt::AutoConnection,如果您使用 Qt::DirectConnection,这种连接类型将在 运行 时间内决定如果接收器与信号发射处位于同一根电线中,则使用 Qt::QueuedConnection。如您所知,Worker 没有父级,因此要使它的生命周期等于 class,它必须是它的一个属性,否则它将是一个被消除的局部变量。另一方面,请注意 QThread 接收父窗口而不是 MainWindow,因此 QThread 将存在于 GUI 线程中,但它处理辅助线程。


有了 Worker 的概念,change_text 信号最好不再属于 GUI,而是更好地解耦对象的 worker。

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.button = QPushButton('Push me!', self)
        self.setCentralWidget(self.button)

        print('main running in:', QThread.currentThread())
        self.worker = Worker()
        thread = QThread(self)
        self.worker.moveToThread(thread)
        thread.start()
        self.button.clicked.connect(self.worker.do_something_slow)
        self.worker.change_text.connect(self.display_changes)

    @pyqtSlot(str)
    def display_changes( self, text ):
        self.button.setText(text)

class Worker(QObject):
    change_text = pyqtSignal(str)

    @pyqtSlot()
    def do_something_slow( self ):
        print('Slot doing stuff in:', QThread.currentThread())
        import time
        time.sleep(5)
        self.change_text.emit('I did something')