如何检测Qt5中的QObject :: moveToThread()故障? [英] How to detect QObject::moveToThread() failure in Qt5?

查看:176
本文介绍了如何检测Qt5中的QObject :: moveToThread()故障?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

在一些答案之后,我想在此强调一下,我完全知道可以在调用moveToThread之前测试parent是否为0.我正在寻找凭经验确定moveToThread调用实际上成功的可能方法.

解决方案

要可靠地获取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移动到另一个线程.为此,您只需要在要移动的QObject层次结构的上调用moveToThread(),所有孩子也将被移动(这是为了确保父母及其子女子代始终在同一线程上).我知道,这是学术上的区别.只是在这里很详尽.

  2. QObjectthread()不是== QThread::currentThread()时,moveToThread()调用也可能失败(即,您只能 push 一个对象 ,但不能从另一个线程(另一个线程).

  3. 最后一句话是说谎的孩子.您可以 拉出一个对象,前提是该对象之前未与任何线程分离(通过调用moveToThread(nullptr).

  4. 当线程亲和力发生变化时,将向对象发送QEvent::ThreadChange事件.

现在,您的问题是如何可靠地检测到这一举动发生了.答案是:这并不容易.首先,比较QObject::thread()返回值 moveToThread()调用与moveToThread()的参数不是一个好主意,因为QObject::thread()不是(已记录为)线程安全(请参见实现).

为什么会有问题?

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

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

前进的唯一方法是使用ThreadChange事件.在检查完所有失败情况之后,将发送该事件,但至关重要的是,仍然从原始线程发送该事件(请参见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.

解决方案

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

这篇关于如何检测Qt5中的QObject :: moveToThread()故障?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆