1

I've been playing with threading in Qt and while testing signals for objects in another thread I faced the behaviour I can't explain.

Context:

import threading
from PyQt5.QtCore import QObject, QThread

class MyObject(QObject):
    def __init__(self):
        super(MyObject, self).__init__()
        print 'obj init, thread id: {}'.format(threading.current_thread().ident)
        print 'obj init, thread affinity: {}'.format(self.thread())

    def print_thread_id(self):
        print 'thread id: {}'.format(threading.current_thread().ident)
        print 'thread affinity: {}'.format(self.thread())

obj = MyObject()
thread = QThread()
# thread.started.connect(obj.print_thread_id)
obj.moveToThread(thread)
thread.started.connect(obj.print_thread_id)
thread.start()
print 'thread started'

So we have a QObject, that is initialized in a main thread and then moved to a worker thread. We want to check new thread id and that thread affinity has changed.

If we connect a thread.started signal afterobj.moveToThread() we can see new values for thread id and thread affinity:

obj init, thread id: 3296
obj init, thread affinity: <PyQt5.QtCore.QThread object at 0x0000000004819D38>
thread started
thread id: 20040
thread affinity: <PyQt5.QtCore.QThread object at 0x0000000004819318>

But if we try to connect a signal to print_thread_id before moving obj to a worker thread, it seems that the signal is not emitted:

obj init, thread id: 3296
obj init, thread affinity: <PyQt5.QtCore.QThread object at 0x0000000004819D38>
thread started

Why? What exactly happens with a QObject during moveToThread() and how it affects signals and slots?

Update:

Not sure if it's related, but the described behaviour is observed when the main thread where obj is created is not a Qt GUI thread. If we do the same in a Qt GUI thread:

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    obj = MyObject()
    thread = QThread()
    thread.started.connect(obj.print_thread_id)
    obj.moveToThread(thread)
    thread.start()
    print 'thread started'

than the signal is emitted, but the result is unexpected:

obj init, thread id: 4592
obj init, thread affinity: <PyQt5.QtCore.QThread object at 0x0000000007DF0CA8>
thread started
thread id: 4592
thread affinity: <PyQt5.QtCore.QThread object at 0x0000000007DF0CA8>

Thread id and thread affinity are the same as when the object instance was created. It looks like the object was not moved to a worker thread.

wombatonfire
  • 4,585
  • 28
  • 36
  • @ekhumoro, I saw that question before posting, and I don't think it's a duplicate. My question is not about the signal that is not emitted (if you look at the update, in another context it's emitted, but the behaviour is still unclear), but about the effect ```moveToThread()``` has on the QObject, its signals and slots. – wombatonfire Jan 18 '17 at 20:48
  • 1
    It really is an exact duplicate - the problem is with the slots, not the signals (due to an implementation detail of PyQt). The other issues in your examples are to do with what you are printing. A `QThread` is not a thread; it is a wrapper object around the real thread supplied by the OS. Also, there is a PyQt wrapper around the Qt wrapper as well. Printing `self.thread()` tells you absolutely nothing about thread affinity. Use: `print int(QThread.currentThreadId())`. And set the object name of the `QThread` you created, and then do `print(self.thread().objectName())`. – ekhumoro Jan 18 '17 at 21:47
  • Well, I have to agree, it's a duplicate. ```proxy->moveToThread(rx_qobj->thread());``` part in your answer puts everything into place. And this seems like the only reason for the observed behavior and it has nothing to do with printing. In my first example proxy object is on a thread without event loop -> signal is not emitted. In my second example proxy object is on a thread with event loop -> signal is emitted, and I see the id of the main thread with the proxy object. Now it's clear. Thank you! – wombatonfire Jan 18 '17 at 22:40
  • Okay - but really, printing the repr of a thread object is not a reliable way of checking thread affinity. – ekhumoro Jan 18 '17 at 22:54
  • Probably, "thread affinity" is a wrong label here. While trying to understand what was going on, I was interested in a thread identifier for a QObject. That's why I used ```threading.current_thread().ident```. ```print int(QThread.currentThreadId())``` basically shows the same. ```QThread.thread()``` I found in Qt documentation and for it I only looked at the memory address. It should be different, if the object is moved to another thread. – wombatonfire Jan 18 '17 at 23:07
  • 1
    Yes, it's that last one I've found to be inconsistent. In my testing, the address of the PyQt object returned by `obj.thread()` or `QThread.currentThread()` never changes, but `QThread.currentThreadId()` usually does. I say "usually", because it doesn't change immediately after `moveToThread()`. The only really consistent method I've found so far is to use `sip.unwrapinstance(thread)`, which gets the underlying C++ address. – ekhumoro Jan 19 '17 at 01:26

0 Answers0