Search code examples
pythonqtpython-2.7pysideqthread

migrating from Inherited QThread to Worker model


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)

Solution

  • 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: