5

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

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

编辑:我想在这里强调一些答案,我完全知道我可以在调用 moveToThread 之前测试 parent 是否为 0。我正在寻找可能的方法来凭经验确定moveToThread呼叫实际上成功了。

4

2 回答 2

8

为了可靠地获得 的结果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带有父级的 a 移动到另一个线程。为此,您只需调用要移动的层次结构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,而不仅仅是您可以或想要子类化的那些。但是有一个问题:一旦发送了事件,事件处理就切换到新线程,因此事件过滤器对象将被两个线程敲击,这绝不是一个好主意。简单的解决方案:使事件过滤器成为要移动的对象的子对象,然后它会随之移动。另一方面,这给您带来了如何控制存储生命周期的问题,以便即使移动的对象在到达新线程时立即被删除,您也可以获得结果。长话短说:存储需要是对旧线程中变量的引用,而不是被移动对象或事件过滤器的成员变量。然后对存储的所有访问都来自原始线程,并且没有竞争。

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

于 2015-01-01T21:01:39.973 回答
2

QObject::moveToThread只有当它有父母时才会失败。如果它的父母是NULL那么你可以移动它,否则你不能。

编辑:

您可以做的是在调用QObject::thread并检查它是否真的改变了它的亲和力之后,您可以检查对象的线程亲和力。moveToThread

QThread *pThread = new QThread;

QObject *pObject = new QObject;

{
    QMutexLocker locker(&mutex);

    pObject->moveToThread(pThread);

    if(pObject->thread() != pThread)
    {
        qDebug() << "moveToThread failed.";
    }
}
于 2014-11-01T20:07:24.467 回答