QObject::moveToThread()上针对 Qt5.3 的文档说明,如果对象有父级,moveToThread()
方法可能会失败。我如何在代码中检测到这个失败?
我意识到只是确保我的对象没有父对象可能已经足够好了,但作为防御性编程实践,我想测试可能失败的所有调用的返回值。
编辑:我想在一些答案之后强调一下,我完全清楚我可以在调用moveToThread之前测试父是否为0。我正在寻找可行的方法来根据经验确定moveToThread
调用实际上是成功的。
答案 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;
}
长篇故事:
文档错误。您可以将QObject
与父级移动到另一个线程。为此,您只需要在要移动的moveToThread()
层次结构的根上调用QObject
,所有子项也将被移动(这是为了确保父母和子女总是在同一个线索上。我知道这是学术上的区别。只是在这里彻底。
当moveToThread()
QObject
thread()
不是== QThread::currentThread()
时,moveToThread(nullptr)
来电也会失败(即你只能< em>将对象推送到,但从另一个线程拉一个)。
最后一句是lie-to-children。你可以拉出一个对象,如果它已经与任何线程分离(通过调用QEvent::ThreadChange
。
当线程相关性发生变化时,会向对象发送QObject::thread()
事件。
现在,您的问题是如何可靠地检测到移动的发生。答案是:这并不容易。显而易见的第一件事是,在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.";
}
}