How to detect QObject::moveToThread() failure in Qt5?

强颜欢笑 提交于 2020-01-23 01:10:06

问题


The documentation on QObject::moveToThread() for Qt5.3 explains that the moveToThread() method can fail if the object has a parent. How would I detect this failure in my code?

I realize that simply making sure that my object does not have a parent first is probably good enough, but as a defensive programming practice I would like to test the return value from all calls that may fail.

EDIT: I want to stress here after some answers that I am fully aware that I can test if parent is 0 before calling moveToThread. I am looking for possible ways to determine empirically that the moveToThread call actually succeeded.


回答1:


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:

  1. 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.

  2. 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).

  3. 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).

  4. 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 :)




回答2:


QObject::moveToThread fails only if it has a parent. If its parent is NULL then you can move it, else you can't.

EDIT:

What you could do is you can check the object's thread affinity after you called moveToThread by calling QObject::thread and checking if it had really changed its affinity.

QThread *pThread = new QThread;

QObject *pObject = new QObject;

{
    QMutexLocker locker(&mutex);

    pObject->moveToThread(pThread);

    if(pObject->thread() != pThread)
    {
        qDebug() << "moveToThread failed.";
    }
}


来源:https://stackoverflow.com/questions/26692649/how-to-detect-qobjectmovetothread-failure-in-qt5

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!