使用QObject从Python线程发出信号 [英] Emitting signals from a Python thread using QObject

查看:460
本文介绍了使用QObject从Python线程发出信号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

与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方法将在哪个线程中执行?如果我在MyObject中使用QThread而不是python线程会有所不同吗?

我正在使用PyQt5和Python 3.

解决方案

默认情况下,Qt Qt文档声明以下内容:

注意:Qt的线程类是通过本机线程实现的 蜜蜂;例如Win32和pthreads.因此,它们可以与 相同的本机API的线程.

通常,如果文档指出Qt API是线程安全的,该保证适用于使用同一本机库创建的所有线程,而不仅仅是Qt本身创建的那些线程.这意味着使用 postEvent() invoke() .

因此,在发出跨线程信号时,使用threading.ThreadQThread并没有真正的区别,只要Python和Qt都使用相同的底层本机线程库即可.这表明在PyQt应用程序中首选使用QThread的一个可能原因是 portability ,因为这样就不会混合使用不兼容的线程实现.但是,考虑到Python和Qt都是故意设计为跨平台的,因此该问题在实践中极不可能出现.


关于slot将在哪个线程中执行的问题-对于Python和Qt,它将在 main 线程中执行.相比之下,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
 

I would like to know what are the consequences of emitting a signal from a regular python thread within a QObject, compared with a QThread.

See the following class:

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

Now, assuming that in the GUI thread, I have:

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

def slot(self):
    # Do something

the slot is indeed executed when the signal is emitted. However, I would like to know which thread will the slot method be executed in? Would it be any different if I used a QThread instead of a python thread in MyObject?

I am using PyQt5 and Python 3.

解决方案

By default, Qt automatically queues signals when they are emitted across threads. To do this, it serializes the signal parameters and then posts an event to the event-queue of the receiving thread, where any connected slots will eventually be executed. Signals emitted in this way are therefore guaranteed to be thread-safe.

With regard to external threads, the Qt docs state the following:

Note: Qt's threading classes are implemented with native threading APIs; e.g., Win32 and pthreads. Therefore, they can be used with threads of the same native API.

In general, if the docs state that a Qt API is thread-safe, that guarantee applies to all threads that were created using the same native library - not just the ones that were created by Qt itself. This means it is also safe to explicitly post events to other threads using such thread-safe APIs as postEvent() and invoke().

There is therefore no real difference between using threading.Thread and QThread when it comes to emitting cross-thread signals, so long as both Python and Qt use the same underlying native threading library. This suggests that one possible reason to prefer using QThread in a PyQt application is portability, since there will then be no danger of mixing incompatible threading implementations. However, it is highly unlikely that this issue will ever arise in practice, given that both Python and Qt are deliberately designed to be cross-platform.


As to the question of which thread the slot will be executed in - for both Python and Qt, it will be in the main thread. By contrast, the run method will be executed in the worker thread. This is a very important consideration when doing multi-threading in a Qt application, because it is not safe to perform gui operations outside the main thread. Using signals allows you to safely communicate between the worker thread and the gui, because the slot connected to the signal emitted from the worker will be called in the main thread, allowing you to update the gui there if necessary.

Below is a simple script that shows which thread each method is called in:

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_())

Output:

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

这篇关于使用QObject从Python线程发出信号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆