简体   繁体   English

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

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

The documentation on QObject::moveToThread() for Qt5.3 explains that the moveToThread() method can fail if the object has a parent. Qt5.3的QObject :: moveToThread() 文档解释了如果对象有父对象, moveToThread()方法可能会失败。 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. 编辑:我想在一些答案之后强调一下,我完全清楚我可以在调用moveToThread之前测试父是否为0。 I am looking for possible ways to determine empirically that the moveToThread call actually succeeded. 我正在寻找可行的方法来根据经验确定moveToThread调用实际上是成功的。

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: 要可靠地获取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;
 }

Long story: 很长的故事:

  1. The documentation is wrong. 文档错了。 You can move a QObject with a parent to another thread. 可以将带有父项的QObject移动到另一个线程。 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). 为此,您只需要在要移动的QObject层次结构的上调用moveToThread() ,并且也将移动所有子项(这是为了确保父项及其子项始终位于同一线程上)。 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). QObjectthread()不是== QThread::currentThread()时, moveToThread()调用也会失败(即你只能一个对象 送到另一个线程,但不能另一个线程中拉出一个)。

  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) . 如果之前已经与任何线程分离(通过调用moveToThread(nullptr)可以拉取对象。

  4. When the thread affinity changes, the object is sent a QEvent::ThreadChange event. 当线程关联性发生更改时,会向对象发送一个QEvent::ThreadChange事件。

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 ). 最明显的第一件事情,比较QObject::thread()的返回值 moveToThread()调用的参数moveToThread()是不是一个好主意,因为QObject::thread()不是(记录是)螺纹-safe(参见实施 )。

Why is that a problem? 为什么这是一个问题?

As soon as moveToThread() returns, the moved-to thread may already have started executing "the object", ie. 一旦moveToThread()返回,移动到的线程可能已经开始执行“对象”,即。 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. 在这种情况下,对原始线程上的QObject::thread()的以下调用将取消引用已删除的数据。 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. 或者新线程将对象移交给另一个线程,在这种情况下,原始线程中对thread()的调用中的成员变量的读取将与针对moveToThread()中的相同成员变量的写入竞争新线程。

Bottomline: Accessing a moveToThread() ed object from the original thread is undefined behaviour. 底线:从原始线程访问moveToThread() ed对象是未定义的行为。 Don't do it. 不要这样做。

The only way forward is to use the ThreadChange event. 前进的唯一方法是使用ThreadChange事件。 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. 您可以通过继承移动到的对象并重新实现QObject::event()或在要移动的对象上安装事件过滤器来检查事件。

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. 当然,事件过滤器方法更好,因为您可以将它用于任何QObject ,而不仅仅是那些您可以或想要子类化的QObject 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 :) 这个实现留给读者一个练习:)

QObject::moveToThread fails only if it has a parent. QObject::moveToThread只有在有父项时QObject::moveToThread失败。 If its parent is NULL then you can move it, else you can't. 如果它的父级是NULL那么你可以移动它,否则你不能。

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. 你可以做的是你可以通过调用QObject :: thread调用moveToThread来检查对象的线程亲和性 ,并检查它是否真的改变了它的亲和力。

QThread *pThread = new QThread;

QObject *pObject = new QObject;

{
    QMutexLocker locker(&mutex);

    pObject->moveToThread(pThread);

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM