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 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:
Worker
a regular QObject
, not a QThread
QThread
in your App
, start it and move the worker to that threadAnd you should check out these references:
edit:
A few other things I noticed in your code:
threading.currentThread
will not correctly reflect the current qt thread. use QThread.currentThread()
for that.pyqtSlots
, not doing so can be a cause of problems like these.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With