【问题标题】:PyQt wake main thread from non-QThreadPyQt 从非 QThread 唤醒主线程
【发布时间】:2017-02-13 16:45:51
【问题描述】:

我有一个 PyQt 应用程序,它通过回调从外部源接收信息,这些回调是从不受我控制且不是 QThread 的线程调用的。将此类信息传递给主线程而不进行轮询的正确方法是什么?特别是,我想发出一个 Qt 信号,以便在新数据到达时唤醒主线程(或另一个 QThread)。

【问题讨论】:

  • 不太明白,非qt线程的源码不能改吗?完成后发出信号?
  • 我唯一能控制的是回调函数本身,它是一个普通的python函数。 IE。我可以发出信号,但这会被允许吗?将运行该函数的线程不是由我的代码创建的,并且根本没有 Qt 的概念。所以也许我应该问一下发出信号是否是线程安全的(即使来自非 Qt 线程)?
  • 我不知道这是最佳做法还是不好的做法,但我会尝试这样做,从您的回调中发送信号。我认为它应该工作。 (Qt 线程安全取决于连接类型:doc.qt.io/qt-4.8/…
  • 您正在链接的文档状态“另一方面,您可以安全地从 QThread::run() 实现中发出信号,因为信号发射是线程安全的。”第二部分说你的建议很好,但第一部分似乎再次将此语句限制为 QThreads...
  • 线程安全意味着即使它不在基于QThread的线程中也应该可以工作。

标签: python multithreading qt pyqt qthread


【解决方案1】:

信号的默认连接类型是Qt.AutoConnection,文档是这样描述的:

如果信号是从与接收不同的线程发出的 对象,信号被排队,表现为 Qt::QueuedConnection。 否则,直接调用槽,表现为 Qt::DirectConnection。连接的类型是确定的,当 发出信号。

所以在发出信号之前,Qt 会简单地比较发送者和接收者的当前线程亲和性,然后再决定是否将其排队。 如何底层线程最初是如何启动的并不重要。

这是一个使用 python 工作线程的简单演示:

import sys, time, threading
from PyQt4 import QtCore, QtGui

class Worker(object):
    def __init__(self, callback):
        self._callback = callback
        self._thread = None

    def active(self):
        return self._thread is not None and self._thread.is_alive()

    def start(self):
        self._thread = threading.Thread(target=self.work, name='Worker')
        self._thread.start()

    def work(self):
        print('work: [%s]' % threading.current_thread().name)
        for x in range(5):
            time.sleep(1)
            self._callback(str(x))

class Window(QtGui.QPushButton):
    dataReceived = QtCore.pyqtSignal(str)

    def __init__(self):
        super(Window, self).__init__('Start')
        self.clicked.connect(self.start)
        self.dataReceived.connect(self.receive)
        self.worker = Worker(self.callback)

    def receive(self, data):
        print('received: %s [%s]' % (data, threading.current_thread().name))

    def callback(self, data):
        print('callback: %s [%s]' % (data, threading.current_thread().name))
        self.dataReceived.emit(data)

    def start(self):
        if self.worker.active():
            print('still active...')
        else:
            print('start: [%s]' % threading.current_thread().name)
            self.worker.start()

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.show()
    print('show: [%s]' % threading.current_thread().name)
    sys.exit(app.exec_())

典型输出:

$ python test.py
show: [MainThread]
start: [MainThread]
work: [Worker]
callback: 0 [Worker]
received: 0 [MainThread]
still active...
callback: 1 [Worker]
received: 1 [MainThread]
still active...
callback: 2 [Worker]
received: 2 [MainThread]
still active...
callback: 3 [Worker]
received: 3 [MainThread]
callback: 4 [Worker]
received: 4 [MainThread]

【讨论】:

  • 你说Qt“比较当前发送者和接收者的线程亲和性”:如何为发送者定义线程亲和性?它是实际线程(例如threading.current_thread())还是发送QObject 所在的线程(在您的示例中,它是在创建Worker 时确定的,这将是错误的线程)?前者更有意义并使您的解决方案正确,而后者符合 Qt doc 对“线程亲和力”的定义:doc.qt.io/qt-5/qobject.html#thread-affinity
  • 对不起,我的错误,你的例子中的发送对象实际上与接收对象相同,它是Window(信号dataReceived和插槽callback都是@987654331的一部分@)。因此,如果它确实按照文档使用“线程关联”,它将(错误地)执行直接调用。因此,我得出结论,Qt 确实将当前运行的线程(而不是发送者的线程亲和性)与接收者的线程亲和性进行了比较。
  • @burnpanck。是的,在我的示例中,“发送者”是指工作人员,因为这实际上是调用回调函数(它有一个引用)。
【解决方案2】:

如果您只是轮询外部设备或库,我会以同样的方式处理它。创建一个单独的工作线程来处理回调,并向主 GUI 线程发出信号。

class Worker(QObject):

    data_ready = pyqtSignal(object, object)

    def callback(self, *args, **kwargs):
        self.data_ready.emit(args, kwargs)


class Window(...)

    def __init__(self):
        ...
        self.worker = Worker(self)
        # Connect worker callback function to your external library
        library.register_callback(self.worker.callback) #??
        ...

        # Mover worker to thread and connect signal
        self.thread = QThread(self)
        self.worker.data_ready.connect(self.handle_data)
        self.worker.moveToThread(self.thread)
        self.thread.start()

    @pyqtSlot(object, object)
    def handle_data(self, args, kwargs):
        # do something with data

【讨论】:

  • 线程self.thread真的需要吗?它会做什么? Worker.callback 函数当然仍会在 library 创建的未知外星线程上执行...
猜你喜欢
  • 1970-01-01
  • 2018-12-04
  • 2013-12-03
  • 2014-07-15
  • 2017-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多