2

So having followed my best understanding of how to correctly use threads in Qt, I've written a small toy example in which a QObject is moved to a running thread and a signal is called on that object when the window is clicked.

The problem I'm having is the slot is never called in the correct thread - it is always called in the main thread.

This is implemented with the following code:

from PyQt5 import QtWidgets, QtCore

class WidgetWithInput(QtWidgets.QWidget):

    widgetClicked = QtCore.pyqtSignal()

    def mousePressEvent(self, event):
        self.widgetClicked.emit()

class MyObject(QtCore.QObject):

    aSignal = QtCore.pyqtSignal()

    def __init__(self, *args, **kwargs):
        super(MyObject, self).__init__(*args, **kwargs)

        self.aSignal.connect(self.onASignal)

    def onASignal(self):
        print('MyObject signal thread: %s'
               % str(int(QtCore.QThread.currentThreadId())))


class SimpleDisplay(QtWidgets.QMainWindow):
    ''' Class presenting the display subsytem
    '''

    def __init__(self, my_object, *args, **kwargs):

        super(SimpleDisplay, self).__init__(*args, **kwargs)

        self._my_object = my_object

        self._widget = WidgetWithInput()

        self.setCentralWidget(self._widget)
        self._widget.widgetClicked.connect(self._do_something)

    def _do_something(self):
        self._my_object.aSignal.emit()

def main(run_until_time=None):

    import sys
    app = QtWidgets.QApplication(sys.argv)
    print ('main thread: %s' % str(int(QtCore.QThread.currentThreadId())))

    concurrent_object = MyObject()
    display = SimpleDisplay(concurrent_object)

    myobject_thread = QtCore.QThread()
    myobject_thread.start()

    concurrent_object.moveToThread(myobject_thread)

    display.show()

    exit_status = app.exec_()

    myobject_thread.quit()
    myobject_thread.wait()

    sys.exit(exit_status)

if __name__ == '__main__':
    main()

Which outputs something like:

main thread: 139939383113472
MyObject signal thread: 139939383113472

I would expect those two printed thread ids to be different.

Am I missing something here? Explicitly setting the signal to be queued doesn't change anything.

Henry Gomersall
  • 8,434
  • 3
  • 31
  • 54
  • 1
    You appear to be triggering the `MyObject.aSignal` signal *before* moving the `MyObject` instance to the new thread. Hence the output you see. – G.M. Jun 02 '17 at 12:14
  • @G.M. I was of the understanding that signals are received in the thread of the object. I'm happy to be wrong on this, but it isn't clear to me how I connect the signals up in the correct thread. I've tried a couple of things, like emitting a new signal on the `ThreadChange` event (which is notionally the last event before the thread changes), a handler for which connects up the signals, but that didn't work (same result as before). I can get it to work by doing it explicitly in the `main` function, but this requires my `main` to have knowledge that should be restricted to the class IMO. – Henry Gomersall Jun 02 '17 at 17:02
  • Further to this, I can fix the problem by passing the thread to `MyObject`, and then use `self.moveToThread` in the `__init__`. This seems slightly mucky to me as it requires MyObject to have knowledge the thread it's in. – Henry Gomersall Jun 02 '17 at 17:10
  • The order in which you connect signals and move objects to a thread matters unless you decorate the slots with the `pyqtSlot` function. The question I flagged this as a duplicate of (see [here](https://stackoverflow.com/q/20752154/1994235)) is for PyQt4, but I believe it translates to PyQt5 as well. – three_pineapples Jun 03 '17 at 01:00

1 Answers1

1

With thanks to @three_pinapples, his suggestion in the comments solves the problem: The slot needs to be decorated with QtCore.pyqtSlot:

class MyObject(QtCore.QObject):

    aSignal = QtCore.pyqtSignal()

    def __init__(self, *args, **kwargs):
        super(MyObject, self).__init__(*args, **kwargs)

        self.aSignal.connect(self.onASignal)

    @QtCore.pyqtSlot()
    def onASignal(self):
        print('MyObject signal thread:\t %s'
               % str(int(QtCore.QThread.currentThreadId())))
Henry Gomersall
  • 8,434
  • 3
  • 31
  • 54