我在我的应用程序中为每个“长”操作使用QtConcurrent API。 它工作得很好,但我在创建QObjects时遇到了一些问题。
考虑这段代码,它使用一个线程来创建一个“Foo”对象:
QFuture<Foo*> = QtConcurrent::run([=]()
{
Data* data = /*long operation to acquire the data*/
Foo* result = new Foo(data);
return result;
});
它运行良好,但如果“Foo”类派生自QObject类,则“result”实例属于创建该对象的QThread。
因此,要在“result”实例中正确使用signal / slot,应该执行以下操作:
QFuture<Foo*> = QtConcurrent::run([=]()
{
Data* data = /*long operation to acquire the data*/
Foo* result = new Foo(data);
// Move "result" to the main application thread
result->moveToThread(qApp->thread());
return result;
});
现在,所有工作都得到了应用,我认为这是正常行为和名义解决方案。
我有很多这种代码,有时会创建对象,也可以创建对象。其中大多数是通过“moveToThread”调用正确创建的。
但有时候,我会想念一个“moveToThread”电话。
然后,很多事情看起来都不起作用(因为这个对象插槽已经“坏了”),没有任何Qt警告。
现在,我有时会花很多时间来弄清楚为什么某些东西不起作用,在理解它之前只是因为不再在特定的对象实例上调用插槽。
有没有办法帮助我预防/检测/调试这种情况? 例如:
由于
答案 0 :(得分:2)
可以跟踪对象在线程之间的移动。在将对象移动到新线程之前,会向其发送ThreadChange
事件。您可以过滤该事件并运行代码以记录对象何时离开线程。但是要知道对象是否随处可见还为时尚早。要检测到这一点,您需要在对象的队列中发布一个metacall(请参阅this question),以便在新线程中恢复对象的事件处理后立即执行。您还可以附加到QThread::finished
,以便有机会查看您的对象列表,并检查它们中是否有任何一个存在于即将死亡的线程中。
但所有这些都是相当复杂的:每个线程都需要自己的跟踪器/过滤器对象,因为事件过滤器必须存在于对象的线程中。我们可能正在谈论超过200行代码来正确处理所有极端情况。
相反,您可以利用RAII并使用将线程关联性作为资源管理的句柄来保存您的对象(因为它是一个!):
// https://github.com/KubaO/stackoverflown/tree/master/questions/thread-track-38611886
#include <QtConcurrent>
template <typename T>
class MainResult {
Q_DISABLE_COPY(MainResult)
T * m_obj;
public:
template<typename... Args>
MainResult(Args&&... args) : m_obj{ new T(std::forward<Args>(args)...) } {}
MainResult(T * obj) : m_obj{obj} {}
T* operator->() const { return m_obj; }
operator T*() const { return m_obj; }
T* operator()() const { return m_obj; }
~MainResult() { m_obj->moveToThread(qApp->thread()); }
};
struct Foo : QObject { Foo(int) {} };
您可以按值返回MainResult
,但必须明确指定仿函数的返回类型:
QFuture<Foo*> test1() {
return QtConcurrent::run([=]()->Foo*{ // explicit return type
MainResult<Foo> obj{1};
obj->setObjectName("Hello");
return obj; // return by value
});
}
或者,您可以返回调用MainResult
的结果;它本身就是一个函子,可以节省一些打字,但这可能被视为黑客攻击,也许你应该将operator()()
转换为一个短名称的方法。
QFuture<Foo*> test2() {
return QtConcurrent::run([=](){ // deduced return type
MainResult<Foo> obj{1};
obj->setObjectName("Hello");
return obj(); // return by call
});
}
虽然最好将对象与句柄一起构造,但也可以将实例指针传递给句柄的构造函数:
MainResult<Foo> obj{ new Foo{1} };