To reliably get the result of moveToThread()
, catch the ThreadChange
event of the object undergoing the move (by overriding QObject::event()
or installing an event filter), and store whether the event has been seen in a reference to a local variable:
static bool moveObjectToThread(QObject *o, QThread *t) {
class EventFilter : public QObject {
bool &result;
public:
explicit EventFilter(bool &result, QObject *parent = nullptr)
: QObject(parent), result(result) {}
bool eventFilter(QObject *, QEvent *e) override {
if (e->type() == QEvent::ThreadChange)
result = true;
return false;
}
};
bool result = false;
if (o) {
o->installEventFilter(new EventFilter(result, o));
o->moveToThread(t);
}
return result;
}
Long story:
The documentation is wrong. You can move a QObject
with a parent to another thread. To do so, you just need to call moveToThread()
on the root of the QObject
hierarchy you want to move, and all children will be moved, too (this is to ensure that parents and their children are always on the same thread). This is an academic distinction, I know. Just being thorough here.
The moveToThread()
call can also fail when the QObject
's thread()
isn't == QThread::currentThread()
(ie. you can only push an object to, but not pull one from another thread).
The last sentence is a lie-to-children. You can pull an object if it has before been dissociated with any thread (by calling moveToThread(nullptr)
.
When the thread affinity changes, the object is sent a QEvent::ThreadChange
event.
Now, your question was how to reliably detect that the move happened. The answer is: it's not easy. The obvious first thing, comparing the QObject::thread()
return value after the moveToThread()
call to the argument of moveToThread()
is not a good idea, since QObject::thread()
isn't (documented to be) thread-safe (cf. the implementation).
Why is that a problem?
As soon as moveToThread()
returns, the moved-to thread may already have started executing "the object", ie. events for that object. As part of that processing, the object might be deleted. In that case the following call to QObject::thread()
on the original thread will dereference deleted data. Or the new thread will hand off the object to yet another thread, in which case the read of the member variable in the call to thread()
in the original thread will race against the write to the same member variable within moveToThread()
in the new thread.
Bottomline: Accessing a moveToThread()
ed object from the original thread is undefined behaviour. Don't do it.
The only way forward is to use the ThreadChange
event. That event is sent after all failure cases have been checked, but, crucially, still from the originating thread (cf. the implementation; it would also be just plain wrong to send such an event if no thread change actually happened).
You can check for the event either by subclassing the object you move to and reimplementing QObject::event()
or by installing an event filter on the object to move.
The event-filter approach is nicer, of course, since you can use it for any QObject
, not just those you can or want to subclass. There's a problem, though: as soon as the event has been sent, event processing switches to the new thread, so the event filter object will be hammered from two threads, which is never a good idea. Simple solution: make the event filter a child of the object to move, then it will be moved along with it. That, on the other hand, gives you the problem how to control the lifetime of the storage so you can get the result even if the moved object is immediately deleted when it reaches the new thread. To make a long story short: the storage needs to be a reference to a variable in the old thread, not a member variable of the object being moved or the event filter. Then all accesses to the storage are from the originating thread, and there are no races.
But, but... isn't that still unsafe? Yes, but only if the object is moved again to another thread. In that case, the event filter will access the storage location from the first moved-to thread, and that will race with the read access from the originating thread. Simple solution: de-install the event filter after it has fired once. That implementation is left as an exercise to the reader :)