PyQt | 信号未在QThread中处理,但在主线程中处理

jpc*_*cgt 4 pyqt qthread

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

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_())
Run Code Online (Sandbox Code Playgroud)

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

[<_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
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?不幸的是,这方面的PyQt文档是非常不完整和矛盾的.

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

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_())
Run Code Online (Sandbox Code Playgroud)

结果如下:

[<_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
Run Code Online (Sandbox Code Playgroud)

mat*_*ata 10

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

如果希望信号由不同的线程处理,首先需要使用它的moveToThread方法将worker对象移动到该线程.

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

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

你应该看看这些参考:


编辑:

我在你的代码中注意到的一些其他事情:

  • 你正在混合python线程和qt线程.threading.currentThread无法正确反映当前的qt线程.使用QThread.currentThread()了点.
  • 装饰你正在调用的插槽pyqtSlots,不这样做会导致像这样的问题.
  • 使用新的风格信号.PyQt5不再支持旧式信号,新式信号更容易使用.

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

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_())
Run Code Online (Sandbox Code Playgroud)

我设置了线程的objectNames以使输出更易读:

[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