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

Ska*_*dix 2 python pyqt qthread qt-signals pyqt5

这是一个基于 ekhumoro在这里这里的回答的后续问题。


我想明白,当一个插槽被正确定义pyqtSlot并分配给QThread(例如 with moveToThread())时,它将在这个 QThread 中执行,而不是在调用者中执行。此外,还需要与Qt.QueuedConnection或进行连接Qt.AutoConnection

我写了代码来测试这个。我的目标是实现一些非常简单的东西: 在此处输入图片说明
带有按钮的 GUI,它开始一些耗时的工作并返回结果以显示在 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()
Run Code Online (Sandbox Code Playgroud)

但是 .. gui 被阻塞,并且在主线程中调用了插槽。
我错过了什么?必须是小东西(我希望)。

eyl*_*esc 8

问题是您混淆QThreadQt 线程,即 Qt 创建的新线程,但不,QThread处理本机线程的类,只有该run()方法在另一个线程上运行,其他方法存在于线程中在QThread生活中,这是一个QObject

QObject 存在于哪个线程中?

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


要使用的方法QThread是创建一个类,该类继承QThread并覆盖 run 方法并调用start()以便它开始运行run(),在该run()方法中将完成繁重的任务,但在您的情况下不可以使用此形式,因为任务不会被连续执行。比繁重任务更好的选择是 a 的一部分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')
Run Code Online (Sandbox Code Playgroud)

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


使用 Worker 概念,更好的是 change_text 信号不再属于 GUI,而是属于更好地解耦对象的工作器。

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')
Run Code Online (Sandbox Code Playgroud)