【问题标题】:How do I setup Signals and Slots in PyQt with QThreads in both directions?如何在 PyQt 中使用 QThreads 在两个方向上设置信号和插槽?
【发布时间】:2019-03-30 07:49:21
【问题描述】:

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


我想明白了,当使用pyqtSlot 正确定义插槽并分配给QThread(例如使用moveToThread())时,它将在此QThread 而不是调用线程中执行。此外,还需要与Qt.QueuedConnectionQt.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()

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

【问题讨论】:

    标签: python pyqt pyqt5 qthread qt-signals


    【解决方案1】:

    问题是你混淆QThread是一个Qt线程,也就是一个由Qt创建的新线程,但是不是,QThread是一个处理的类 本机线程,只有run()方法在另一个线程上运行,其他方法住在QThread所在的线程,也就是QObject

    QObject 存在于哪个线程中?

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


    使用QThread 的方法是创建一个继承自QThread 的类并覆盖run 方法并调用start() 以便它开始运行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 没有父级,因此要使其具有与类相等的生命周期,它必须是它的属性,否则它将是一个被消除的局部变量。另一方面,请注意 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')
    

    【讨论】:

    • 正是这一点。我想我现在明白了 - 非常感谢您
    猜你喜欢
    • 2012-07-13
    • 2013-09-05
    • 2011-08-16
    • 2011-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多