PyQt |不在 QThread 中处理但在主线程中处理的信号 [英] PyQt | Signals not handled in QThread but in main thread

查看:81
本文介绍了PyQt |不在 QThread 中处理但在主线程中处理的信号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在这个简单的 PyQt 演示程序中,我从主线程发出信号.在工作线程中我连接到它们,但信号处理程序在主线程中运行:

from PyQt4 import QtGui, QtCore进口螺纹从时间导入睡眠导入系统类数据():def __init__(self, a, b):self.a = a自我.b = bdef __str__(self):返回数据有 %d 和 %d" % (self.a, self.b)类工人(QtCore.QThread):def __init__(self, parent):QtCore.QThread.__init__(self)self.p = 父母定义运行(自我):self.connect(self.p, QtCore.SIGNAL("newTask"), self.task)打印 "[%s] 运行 exec_()" % threading.currentThread()self.exec_()定义任务(自我,dataobj):打印[%s] 处理"% threading.currentThread(), dataobj睡觉(3)打印完成",dataobjself.emit(QtCore.SIGNAL("taskDone"), str(dataobj))类应用程序(QtCore.QObject):def __init__(self):QtCore.QObject.__init__(self)self.w = 工人(自我)self.connect(self.w, QtCore.SIGNAL("taskDone"), self.on_task_done)self.w.start()defassign_tasks(self):self.emit(QtCore.SIGNAL("newTask"), Data(3, 4))self.emit(QtCore.SIGNAL("newTask"), Data(5, 6))打印[%s] 任务已发送"% threading.currentThread()@静态方法def on_task_done(objstr):打印[%s] 应用程序:工人完成了"% threading.currentThread(), objstr如果 __name__ == '__main__':app = QtGui.QApplication(sys.argv)a = 应用程序()睡觉(1)a.assign_tasks()睡觉(20)sys.exit(app.exec_())

但结果显示回调是在主线程中运行的:

[<_DummyThread(Dummy-1, started daemon 105564)>] running exec_()[<_MainThread(MainThread, started 105612)>] 处理数据有 3 和 4完成具有 3 和 4 的数据[<_MainThread(MainThread, started 105612)>] App: Worker 完成了具有 3 和 4 的数据[<_MainThread(MainThread, started 105612)>] 处理数据有 5 和 6完成具有 5 和 6 的数据[<_MainThread(MainThread, started 105612)>] App: Worker 完成了具有 5 和 6 的数据[<_MainThread(MainThread, started 105612)>] 发送的任务

我做错了什么?不幸的是,关于此的 PyQt 文档非常不完整且相互矛盾.

如果我使用 moveToThread 技术,我会得到类似的结果:

from PyQt4 import QtGui, QtCore进口螺纹从时间导入睡眠导入系统类数据():def __init__(self, a, b):self.a = a自我.b = bdef __str__(self):返回数据有 %d 和 %d" % (self.a, self.b)类工人(QtCore.QObject):def __init__(self, parent):QtCore.QObject.__init__(self)self.connect(parent, QtCore.SIGNAL("newTask"), self.task)定义任务(自我,dataobj):打印[%s] 处理"% threading.currentThread(), dataobj睡觉(3)打印完成",dataobjself.emit(QtCore.SIGNAL("taskDone"), str(dataobj))定义开始(自我):打印 "[%s] start()" % threading.currentThread()类应用程序(QtCore.QObject):def __init__(self):QtCore.QObject.__init__(self)self.w = 工人(自我)self.t = QtCore.QThread(self)self.w.moveToThread(self.t)self.connect(self.w, QtCore.SIGNAL("taskDone"), self.on_task_done)self.connect(self.t, QtCore.SIGNAL("started()"), self.w.start)self.t.start()defassign_tasks(self):self.emit(QtCore.SIGNAL("newTask"), Data(3, 4))self.emit(QtCore.SIGNAL("newTask"), Data(5, 6))打印[%s] 任务已发送"% threading.currentThread()@静态方法def on_task_done(objstr):打印[%s] 应用程序:工人完成了"% threading.currentThread(), objstr如果 __name__ == '__main__':app = QtGui.QApplication(sys.argv)a = 应用程序()睡觉(1)a.assign_tasks()睡觉(20)sys.exit(app.exec_())

结果:

[<_DummyThread(Dummy-1, 启动守护进程 108992)>] start()[<_MainThread(MainThread, started 107004)>] 处理数据有 3 和 4完成具有 3 和 4 的数据[<_MainThread(MainThread, started 107004)>] App: Worker 完成了具有 3 和 4 的数据[<_MainThread(MainThread, started 107004)>] 处理数据有 5 和 6完成具有 5 和 6 的数据[<_MainThread(MainThread, started 107004)>] App: Worker 完成了具有 5 和 6 的数据[<_MainThread(MainThread, started 107004)>] 发送的任务

解决方案

你的 Worker 对象活"在主线程中,这意味着它们的所有信号都将由主线程的事件循环处理.这些对象是 QThread 的事实并没有改变这一点.

如果您希望信号由不同的线程处理,您首先需要使用 moveToThread 方法.

所以在你的情况下,只有 run 方法在不同的线程中实际执行,task 方法仍然在主线程中执行.改变这种情况的一种方法是:

  • 让你的 Worker 成为一个普通的 QObject,而不是一个 QThread
  • 在您的 App 中创建一个 QThread,启动它并将工作线程移动到该线程
  • 然后将信号发送给工作线程,使其开始处理

您应该查看这些参考资料:

<小时>

我在您的代码中注意到的其他一些内容:

  • 您正在混合使用 python 线程和 qt 线程.threading.currentThread正确反映当前的 qt 线程.使用 QThread.currentThread() .
  • 将您调用的插槽装饰为 pyqtSlots,否则可能会导致此类问题.
  • 使用新风格信号.PyQt5 不再支持旧样式的信号,新样式的信号更容易使用.

所以这是您的代码应该可以工作的一个版本:

from PyQt4 import QtGui, QtCore进口螺纹从时间导入睡眠导入系统类数据():def __init__(self, a, b):self.a = a自我.b = bdef __str__(self):返回数据有 %d 和 %d" % (self.a, self.b)类工人(QtCore.QObject):taskDone = QtCore.pyqtSignal(str)def __init__(self, parent):QtCore.QObject.__init__(self)parent.newTask.connect(self.task)@QtCore.pyqtSlot(对象)定义任务(自我,dataobj):打印[%s] 处理"% QtCore.QThread.currentThread().objectName(), dataobj睡觉(3)打印完成",dataobjself.taskDone.emit(str(dataobj))@QtCore.pyqtSlot()定义开始(自我):打印 "[%s] start()" % QtCore.QThread.currentThread().objectName()类应用程序(QtCore.QObject):newTask = QtCore.pyqtSignal(object)def __init__(self):QtCore.QObject.__init__(self)self.w = 工人(自我)self.t = QtCore.QThread(self, objectName='workerThread')self.w.moveToThread(self.t)self.w.taskDone.connect(self.on_task_done)self.t.started.connect(self.w.start)self.t.start()defassign_tasks(self):self.newTask.emit(Data(3, 4))self.newTask.emit(Data(5, 6))打印[%s] 发送的任务"% QtCore.QThread.currentThread().objectName()@静态方法def on_task_done(objstr):打印[%s] 应用程序:工人完成了"% QtCore.QThread.currentThread().objectName(), objstr如果 __name__ == '__main__':app = QtGui.QApplication(sys.argv)QtCore.QThread.currentThread().setObjectName('main')a = 应用程序()睡觉(1)a.assign_tasks()从 utils 导入签名sys.exit(app.exec_())

我已经设置了线程的 objectNames 以使输出更具可读性:

<前>[workerThread] 开始()[主要] 发送的任务[workerThread] 处理具有 3 和 4 的数据完成具有 3 和 4 的数据[workerThread] 处理具有 5 和 6 的数据[main] App: Worker 完成了具有 3 和 4 的数据完成具有 5 和 6 的数据[main] 应用程序:Worker 完成了具有 5 和 6 的数据

In this simple PyQt demo program, I emit signals from the main thread. In a worker thread I connect to them, but the signal handlers are run in the main thread:

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


class Data():
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __str__(self):
        return "Data having %d and %d" % (self.a, self.b)

class Worker(QtCore.QThread):
    def __init__(self, parent):
        QtCore.QThread.__init__(self)
        self.p = parent

    def run(self):
        self.connect(self.p, QtCore.SIGNAL("newTask"), self.task)
        print "[%s] running exec_()" % threading.currentThread()
        self.exec_()

    def task(self, dataobj):
        print "[%s] Processing" % threading.currentThread(), dataobj
        sleep(3)
        print "Done with", dataobj
        self.emit(QtCore.SIGNAL("taskDone"), str(dataobj))

class App(QtCore.QObject):
    def __init__(self):
        QtCore.QObject.__init__(self)
        self.w = Worker(self)
        self.connect(self.w, QtCore.SIGNAL("taskDone"), self.on_task_done)
        self.w.start()

    def assign_tasks(self):
        self.emit(QtCore.SIGNAL("newTask"), Data(3, 4))
        self.emit(QtCore.SIGNAL("newTask"), Data(5, 6))
        print "[%s] Tasks sent" % threading.currentThread()

    @staticmethod
    def on_task_done(objstr):
        print "[%s] App: Worker finished with" % threading.currentThread(), objstr

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    a = App()
    sleep(1)
    a.assign_tasks()
    sleep(20)
    sys.exit(app.exec_())

But the result reveals that the callbacks are run in the main thread:

[<_DummyThread(Dummy-1, started daemon 105564)>] running exec_()
[<_MainThread(MainThread, started 105612)>] Processing Data having 3 and 4
Done with Data having 3 and 4
[<_MainThread(MainThread, started 105612)>] App: Worker finished with Data having 3 and 4
[<_MainThread(MainThread, started 105612)>] Processing Data having 5 and 6
Done with Data having 5 and 6
[<_MainThread(MainThread, started 105612)>] App: Worker finished with Data having 5 and 6
[<_MainThread(MainThread, started 105612)>] Tasks sent

What am I doing wrong? Unfortunately, the PyQt docs on this are very incomplete and contradicting.

I'm getting similar results if I use the moveToThread technique:

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

class Data():
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __str__(self):
        return "Data having %d and %d" % (self.a, self.b)

class Worker(QtCore.QObject):
    def __init__(self, parent):
        QtCore.QObject.__init__(self)
        self.connect(parent, QtCore.SIGNAL("newTask"), self.task)

    def task(self, dataobj):
        print "[%s] Processing" % threading.currentThread(), dataobj
        sleep(3)
        print "Done with", dataobj
        self.emit(QtCore.SIGNAL("taskDone"), str(dataobj))

    def start(self):
        print "[%s] start()" % threading.currentThread()

class App(QtCore.QObject):
    def __init__(self):
        QtCore.QObject.__init__(self)

        self.w = Worker(self)
        self.t = QtCore.QThread(self)
        self.w.moveToThread(self.t)
        self.connect(self.w, QtCore.SIGNAL("taskDone"), self.on_task_done)
        self.connect(self.t, QtCore.SIGNAL("started()"), self.w.start)
        self.t.start()

    def assign_tasks(self):
        self.emit(QtCore.SIGNAL("newTask"), Data(3, 4))
        self.emit(QtCore.SIGNAL("newTask"), Data(5, 6))
        print "[%s] Tasks sent" % threading.currentThread()

    @staticmethod
    def on_task_done(objstr):
        print "[%s] App: Worker finished with" % threading.currentThread(), objstr

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    a = App()
    sleep(1)
    a.assign_tasks()
    sleep(20)
    sys.exit(app.exec_())

Which results in:

[<_DummyThread(Dummy-1, started daemon 108992)>] start()
[<_MainThread(MainThread, started 107004)>] Processing Data having 3 and 4
Done with Data having 3 and 4
[<_MainThread(MainThread, started 107004)>] App: Worker finished with Data having 3 and 4
[<_MainThread(MainThread, started 107004)>] Processing Data having 5 and 6
Done with Data having 5 and 6
[<_MainThread(MainThread, started 107004)>] App: Worker finished with Data having 5 and 6
[<_MainThread(MainThread, started 107004)>] Tasks sent

解决方案

Your Worker objects 'live' in the main thread, that means all their signals will be handled by the main thread's event loop. The fact that these objects are QThreads doesn't change that.

If you want signals to be processed by a different thread, you first need to move the worker object to that thread by using it's moveToThread method.

So in your case, only the run method is acutally executed in a different thread, the task method is still executed in the main thread. A way of changing this would be:

  • make your Worker a regular QObject, not a QThread
  • create a QThread in your App, start it and move the worker to that thread
  • then send the signal to the worker that causes it to start processing

And you should check out these references:


edit:

A few other things I noticed in your code:

  • you're mixing python threading and qt threading. threading.currentThread will not correctly reflect the current qt thread. use QThread.currentThread() for that.
  • decorate the slots you're calling as pyqtSlots, not doing so can be a cause of problems like these.
  • use new style signals. Old style signals are no longer supported in PyQt5, and new style signals are much easier and nicer to use.

So here is a version of your code that should work:

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

class Data():
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __str__(self):
        return "Data having %d and %d" % (self.a, self.b)

class Worker(QtCore.QObject):

    taskDone = QtCore.pyqtSignal(str)

    def __init__(self, parent):
        QtCore.QObject.__init__(self)
        parent.newTask.connect(self.task)

    @QtCore.pyqtSlot(object)
    def task(self, dataobj):
        print "[%s] Processing" % QtCore.QThread.currentThread().objectName(), dataobj
        sleep(3)
        print "Done with", dataobj
        self.taskDone.emit(str(dataobj))

    @QtCore.pyqtSlot()
    def start(self):
        print "[%s] start()" % QtCore.QThread.currentThread().objectName()

class App(QtCore.QObject):

    newTask = QtCore.pyqtSignal(object)

    def __init__(self):
        QtCore.QObject.__init__(self)
        self.w = Worker(self)
        self.t = QtCore.QThread(self, objectName='workerThread')
        self.w.moveToThread(self.t)
        self.w.taskDone.connect(self.on_task_done)
        self.t.started.connect(self.w.start)
        self.t.start()

    def assign_tasks(self):
        self.newTask.emit(Data(3, 4))
        self.newTask.emit(Data(5, 6))
        print "[%s] Tasks sent" % QtCore.QThread.currentThread().objectName()

    @staticmethod
    def on_task_done(objstr):
        print "[%s] App: Worker finished with" % QtCore.QThread.currentThread().objectName(), objstr

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    QtCore.QThread.currentThread().setObjectName('main')
    a = App()
    sleep(1)
    a.assign_tasks()
    from utils import sigint
    sys.exit(app.exec_())

I've set the thread's objectNames to make the output better readable:

[workerThread] start()
[main] Tasks sent
[workerThread] Processing Data having 3 and 4
Done with Data having 3 and 4
[workerThread] Processing Data having 5 and 6
[main] App: Worker finished with Data having 3 and 4
Done with Data having 5 and 6
[main] App: Worker finished with Data having 5 and 6

这篇关于PyQt |不在 QThread 中处理但在主线程中处理的信号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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