使用QObject从Python线程发出信号

时间:2015-06-05 22:16:27

标签: python multithreading pyqt pyqt5 signals-slots

我想知道与QThread相比,在QObject中从常规python线程发出信号的后果是什么。

请参阅以下课程:

class MyObject(QtCore.QObject):

    def __init__(self):
        super().__init__()

    sig = pyqtSignal()

    def start(self):
        self._thread = Thread(target=self.run)
        self._thread.start()

    def run(self):
        self.sig.emit()
        # Do something

现在,假设在GUI线程中,我有:

def __init__(self):
    self.obj = MyObject()
    self.obj.sig.connect(self.slot)
    self.obj.start()

def slot(self):
    # Do something

发出信号时确实执行了slot。但是,我想知道slot方法执行哪个线程?如果我在QThread中使用MyObject而不是python线程会有什么不同吗?

我正在使用PyQt5和Python 3。

2 个答案:

答案 0 :(得分:6)

默认情况下,当它们跨线程发出时,Qt automatically queues signals。为此,它将信号参数序列化,然后将事件发布到接收线程的事件队列,最终将执行任何连接的槽。因此,以这种方式发出的信号保证是线程安全的。

关于外部线程,Qt docs state以下内容:

  

注意:Qt的线程类是使用本机线程实现的   蜜蜂;例如,Win32和pthreads。因此,它们可以与之配合使用   相同原生API的线程。

通常,如果文档声明Qt API is thread-safe,该保证适用于使用相同本机库创建的所有线程 - 而不仅仅是Qt本身创建的线程。这意味着使用诸如postEvent()invoke()之类的线程安全API将事件显式发布到其他线程也是安全的。

因此,只要Python和Qt都使用相同的底层本机线程库,在发出跨线程信号时使用threading.ThreadQThread之间没有真正的区别< / em>的。这表明在PyQt应用程序中更喜欢使用QThread的一个可能原因是 portability ,因为这样就不存在混合不兼容的线程实现的危险。但是,鉴于Python和Qt都是故意设计为跨平台的,因此在实践中出现这个问题的可能性极小。

关于将slot执行哪个线程的问题 - 对于Python和Qt,它将位于线程中。相反,run方法将在 worker 线程中执行。在Qt应用程序中执行多线程时,这是一个非常重要的考虑因素,因为在主线程之外执行gui操作是不安全的。使用信号允许您在工作线程和gui之间安全地进行通信,因为连接到工作者发出的信号的插槽将在主线程中被调用,允许您在必要时更新gui。

下面是一个简单的脚本,它显示了调用每个方法的线程:

import sys, time, threading
from PyQt5 import QtCore, QtWidgets

def thread_info(msg):
    print(msg, int(QtCore.QThread.currentThreadId()),
          threading.current_thread().name)

class PyThreadObject(QtCore.QObject):
    sig = QtCore.pyqtSignal()

    def start(self):
        self._thread = threading.Thread(target=self.run)
        self._thread.start()

    def run(self):
        time.sleep(1)
        thread_info('py:run')
        self.sig.emit()

class QtThreadObject(QtCore.QThread):
    sig = QtCore.pyqtSignal()

    def run(self):
        time.sleep(1)
        thread_info('qt:run')
        self.sig.emit()

class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.pyobj = PyThreadObject()
        self.pyobj.sig.connect(self.pyslot)
        self.pyobj.start()
        self.qtobj = QtThreadObject()
        self.qtobj.sig.connect(self.qtslot)
        self.qtobj.start()

    def pyslot(self):
        thread_info('py:slot')

    def qtslot(self):
        thread_info('qt:slot')

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(600, 100, 300, 200)
    window.show()
    thread_info('main')
    sys.exit(app.exec_())

输出:

main 140300376593728 MainThread
py:run 140299947104000 Thread-1
py:slot 140300376593728 MainThread
qt:run 140299871450880 Dummy-2
qt:slot 140300376593728 MainThread

答案 1 :(得分:0)

我想补充:

[-100, 100, 200]

根据我所知的所有文档,这完全可以。如下所示:

[0, 0, 50];

按照@ekhumoro的说法,这两个在功能上是相同的。因为QThread只是QObject,所以run()方法是threading.Thread的target =。

换句话说,MyQThread和MyQObject的信号都是主线程“拥有”的内存,但是可以从子线程访问。

因此以下内容也应安全:

[0, 0, 0]

如果我错了,请纠正我。可以从Qt和/或Riverbank获得有关此行为的官方文档。