PyQt | Signals not handled in QThread but in main thread
pyqt, qthread
Solution
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 `QThread`s 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:
- QObject thread affinity
- Qt threading basics
- Multithreading technologies in Qt
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 `objectName`s 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
Problem
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 ```