So through a lot of help in my previous questions
(Interrupting QThread sleep
and PySide passing signals from QThread to a slot in another QThread) I decided to attempt to change from the inherited QThread
model to the Worker model. I am thinking I should stay with the QThread
model as I had that working, and the other model is not. However I am not sure why the Worker model isn't working for me.
I am attempting to do this please let me know if there is something inherently wrong in my methodology?
I have a QtGui.QWidget
that is my main GUI. I am using a QPushButton
to signal
I have attempted to reduce the code to the basics of where I believe the issue is. I have verified that datagramHandled
Signal
gets emitted but the packet_handled
Slot
doesn't seem to get called.
class myObject(QtCore.QObject):
def __init__(self):
super(myObject, self).__init__()
self.ready=False
@QtCore.Slot()
def do_work(self):
#send a packet
self.ready=False
while not self.ready:
time.sleep(0.01)
@QtCore.Slot(int)
def packet_handled(self, errorCode):
print "Packet received."
self.ready = True
class myWidget(QtGui.QWidget):
datagramHandled = QtCore.Signal(int)
startRunThread = QtCore.Signal()
def __init__(self,parent=None, **kwargs):
super(myWidget, self).__init__(parent=parent)
# Bunch of GUI setup stuff (working)
self.myRunThread = QtCore.QThread()
@QtCore.Slot()
def run_command(self):
self.myRunObj = myObject()
self.myRunObj.moveToThread(self.myRunThread)
self.datagramHandled.connect(self.myRunObj.packet_handled)
self.startRunThread.connect(self.myRunObj.do_work)
self.myRunThread.start()
self.startRunThread.emit()
@QtCore.Slot()
def handle_datagram(self):
#handle the incoming datagram
errorCode = 0
self.datagramHandled.emit(errorCode)
The first issue is that you need to connect your myObject.do_work
method to QThread.started
:
self.myRunThread.started.connect(self.myRunObj.do_work)
Secondly, your do_work
method should include something along these lines to enable event processing (please forgive my rusty PyQt and pseudocode):
def do_work(self):
while someCondition:
#The next two lines are critical for events and queued signals
if self.thread().eventDispatcher().hasPendingEvents():
self.thread().eventDispatcher().processEvents(QEventLoop.AllEvents)
if not self.meetsSomeConditionToContinueRunning():
break
elif self.hasWorkOfSomeKind():
self.do_something_here()
else:
QThread.yieldCurrentThread()
For more on this, check out the docs for QAbstractEventDispatcher
.
The logic here is that when a signal is emitted from one thread (myWidget.datagramHandled
), it gets queued in your worker thread's event loop. Calling processEvents
processes any pending events (including queued signals, which are really just events), invoking the appropriate slots for any queued signals (myRunObj.packet_handled
).
Further reading:
- How To Really, Truly Use QThreads; The Full Explanation
- Threading Basics
There 3 possible ways of distributing the computation/other load with Qt:
- Explicitly putting the load to concrete
QThread
instance. That is thread-based concurrency.
- Implicitly putting the load to pooled
QThread
instance. That is closer to task-based concurrency yet 'manually' managed with your own logic. QThreadPool
class is used for maintaining the pool of threads.
- Starting the task in own threading context we never explicitly manage. That is task-based concurrency and QtConcurrent namespace used. My guess is that task-based concurrency and "worker model" is the same thing (observed your changes). Mind that
QtConcurrent
does offer parallelization for tasks and uses exceptions (which may affect the way you write the code) unlike the rest of Qt.
Given you use PyQt you can also take an advantage of the feature designated for the pattern you want to implement with QtConcurrent for PyQt.
P.S. I see use thread.sleep( interval )
and that is not a good practice and one more indication that the proper technique should be used for implementing 'Worker model'.
An alternative to the solution provided by @JonHarper is to replace your while
loop with a QTimer
. Because you have an event loop running in your worker process now, it can handle QTimer
events correctly (as long as you construct the QTimer
in the relevant thread).
This way, control is returned to the event loop periodically so that other slots can be run when required.