如何在Qt5中检测QObject :: moveToThread()失败?

时间:2014-11-01 20:02:02

标签: c++ multithreading return-value qt5 defensive-programming

QObject::moveToThread()上针对 Qt5.3 的文档说明,如果对象有父级,moveToThread()方法可能会失败。我如何在代码中检测到这个失败?

我意识到只是确保我的对象没有父对象可能已经足够好了,但作为防御性编程实践,我想测试可能失败的所有调用的返回值。

编辑:我想在一些答案之后强调一下,我完全清楚我可以在调用moveToThread之前测试父是否为0。我正在寻找可行的方法来根据经验确定moveToThread调用实际上是成功的。

2 个答案:

答案 0 :(得分:5)

要可靠地获取moveToThread()的结果,请捕获正在进行移动的对象的ThreadChange事件(通过覆盖QObject::event()或安装事件过滤器),并存储事件是否已包含在一个局部变量的引用中被看到:

 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;
 }

长篇故事:

  1. 文档错误。您可以QObject与父级移动到另一个线程。为此,您只需要在要移动的moveToThread()层次结构的上调用QObject,所有子项也将被移动(这是为了确保父母和子女总是在同一个线索上。我知道这是学术上的区别。只是在这里彻底。

  2. moveToThread() QObject thread()不是== QThread::currentThread()时,moveToThread(nullptr)来电也会失败(即你只能< em>将对象推送到,但另一个线程一个)。

  3. 最后一句是lie-to-children。你可以拉出一个对象,如果它已经与任何线程分离(通过调用QEvent::ThreadChange

  4. 当线程相关性发生变化时,会向对象发送QObject::thread()事件。

  5. 现在,您的问题是如何可靠地检测到移动的发生。答案是:这并不容易。显而易见的第一件事是,在moveToThread()调用moveToThread()调用后QObject::thread()返回值moveToThread()的参数进行比较并不是一个好主意,因为QObject::thread()不是(记录为)线程安全的(参见the implementation)。

    为什么会出现问题?

    一旦thread()返回,移动到的线程可能已经开始执行&#34;对象&#34;,即。该对象的事件。作为该处理的一部分,可以删除该对象。在这种情况下,对原始线程上的moveToThread()的以下调用将取消引用已删除的数据。或者新线程将对象移交给另一个线程,在这种情况下,原始线程中对moveToThread()的调用中的成员变量的读取将与{{1}内的同一成员变量的写入竞争在新线程中。

    底线:从原始线程访问ThreadChange ed对象是未定义的行为。 不要这样做。

    前进的唯一方法是使用QObject::event()事件。在检查完所有失败案例之后发送该事件,但是,至关重要的是,仍然来自原始线程(参见the implementation;如果实际上没有发生线程更改,发送这样的事件也是完全错误的)。

    您可以通过继承移动到的对象并重新实现QObject或通过在要移动的对象上安装事件过滤器来检查事件。

    事件过滤器方法当然更好,因为您可以将它用于任何{{1}},而不仅仅是那些您可以或想要子类化的方法。但是有一个问题:一旦事件被发送,事件处理就会切换到新线程,因此事件过滤器对象将从两个线程中敲定,这绝不是一个好主意。简单的解决方案:使事件过滤器成为要移动的对象的子项,然后它将随之移动。另一方面,这会给您提供如何控制存储生命周期的问题,这样即使移动的对象在到达新线程时立即被删除,您也可以获得结果。简而言之:存储需要是旧线程中变量的引用,而不是被移动对象的成员变量或事件过滤器。然后对存储的所有访问都来自原始线程,并且没有比赛。

    但是,但...... 不安全?是的,但仅当对象被再次移动到另一个线程时。在这种情况下,事件过滤器将从第一个移动到的线程访问存储位置,并且将与来自原始线程的读访问权竞争。简单的解决方案:在事件过滤器触发一次后卸载它。该实现留给读者一个练习:)

答案 1 :(得分:2)

QObject::moveToThread只有在有父母的情况下才会失败。如果它的父亲是NULL,那么你可以移动它,否则就不能移动它。

修改

你可以做的是通过调用affinity并检查它是否真的改变了它的亲和力,你可以在调用moveToThread之后检查对象的主题QObject::thread

QThread *pThread = new QThread;

QObject *pObject = new QObject;

{
    QMutexLocker locker(&mutex);

    pObject->moveToThread(pThread);

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