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

Len*_*and 3 c++ multithreading defensive-programming return-value qt5

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

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

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

Mar*_*utz 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;
 }
Run Code Online (Sandbox Code Playgroud)

很长的故事:

  1. 文档错了.您可以QObject父项移动到另一个线程.为此,您只需要调用要移动的层次结构moveToThread()QObject,并且所有子项也将被移动(这是为了确保父项及其子项始终位于同一个线程中).我知道这是学术上的区别.只是在这里彻底.

  2. moveToThread()当呼叫也可能失败QObjectthread()不是== QThread::currentThread()(即你只能的对象,而不是一个另一个线程).

  3. 最后一句是骗子.你可以拉一个对象,如果它已经与任何线程分离(通过调用moveToThread(nullptr).

  4. 当线程关联性发生更改时,会向对象发送一个QEvent::ThreadChange事件.

现在,您的问题是如何可靠地检测到移动的发生.答案是:这并不容易.最明显的第一件事情,比较QObject::thread()返回值后,moveToThread()调用的参数moveToThread()是不是一个好主意,因为QObject::thread()没有(证明是)是线程安全的(参见实施).

为什么这是一个问题?

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

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

前进的唯一方法是使用该ThreadChange事件.在检查所有故障情况之后发送该事件,但是,至关重要的是,仍然来自原始线程(参见实现 ;如果实际上没有发生线程更改,发送这样的事件也是完全错误的).

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

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

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