【问题标题】:Emitting signals from a Python thread using QObject使用 QObject 从 Python 线程发出信号
【发布时间】:2015-08-20 23:57:43
【问题描述】:

我想知道与 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。

【问题讨论】:

    标签: python multithreading pyqt pyqt5 signals-slots


    【解决方案1】:

    我想补充:

    class MyQThread(QThread):
        signal = pyqtSignal() # This thread emits this at some point.
    
    class MainThreadObject(QObject):
        def __init__(self):
            thread = MyQThread()
            thread.signal.connect(self.mainThreadSlot)
            thread.start()
    
        @pyqtSlot()
        def mainThreadSlot(self):
            pass
    

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

    class MyQObject(QObject):
        signal = pyqtSignal()
    
    class MainThreadObject(QObject):
        def __init__(self):
            self.obj = MyQObject()
            self.obj.signal.connect(self.mainThreadSlot)
            self.thread = threading.Thread(target=self.callback)
            self.thread.start()
    
        def callback(self):
            self.obj.signal.emit()
    
        @pyqtSlot()
        def mainThreadSlot(self):
            pass
    

    根据@ekhumoro 的说法,这两者在功能上是相同的。因为QThread 只是一个QObject,它的run() 方法是threading.Thread 的target=。

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

    因此以下也应该是安全的:

    class MainThreadObject(QObject):
        signal = pyqtSignal() # Connect to this signal from QML or Python
    
        def __init__(self):
            self.thread = threading.Thread(target=self.callback)
            self.thread.start()
    
        def callback(self):
            self.signal.emit()
    

    如果我错了,请纠正我。如果有来自 Qt 和/或 Riverbank 的关于这种行为的官方文档,那就太好了。

    【讨论】:

      【解决方案2】:

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

      关于外螺纹,Qt docs state如下:

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

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

      因此,在发出跨线程信号时,使用 threading.ThreadQThread 并没有真正的区别,只要 Python 和 Qt 都使用相同的底层原生线程库。这表明在 PyQt 应用程序中更喜欢使用QThread 的一个可能原因是可移植性,因为这样就不会有混合不兼容的线程实现的危险。但是,鉴于 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
      

      【讨论】:

      • 来自您的链接:“您不能在 Python 线程中使用 Qt(例如,您不能通过 QApplication.postEvent 将事件发布到主线程):您需要一个 QThread工作”(emit() 调用 postEvent(),因此,如果我们相信您链接的帖子,我们不能从 Python 线程.emit()
      • @jfs。如果您阅读了该答案的 cmets,您会发现该声明完全是错误的。没有任何证据支持它,所以你可以放心地忽略它。
      • @jfs。由于它很旧并且有些争议,我已经从我的答案中删除了链接并添加了一些全新的材料。我本来希望找到一个更完整的关于外部线程的官方 Qt 声明,但我认为我找到的那个很清楚。
      猜你喜欢
      • 2018-01-15
      • 2014-09-18
      • 1970-01-01
      • 2013-10-05
      • 1970-01-01
      • 2015-01-03
      • 1970-01-01
      • 1970-01-01
      • 2012-06-04
      相关资源
      最近更新 更多