有没有办法检测QObject是否属于“死”的QThread?

时间:2016-07-27 11:38:09

标签: c++ qt qthread qobject qtconcurrent

故事:

我在我的应用程序中为每个“长”操作使用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警告。

现在,我有时会花很多时间来弄清楚为什么某些东西不起作用,在理解它之前只是因为不再在特定的对象实例上调用插槽。

问题:

有没有办法帮助我预防/检测/调试这种情况? 例如:

  • 每次删除QThread时都会记录一条警告但是有一些活着的对象属于它吗?
  • 每次向QThread被删除的对象发出信号时都会记录警告?
  • 每次向对象(在另一个线程中)发出信号时都会记录警告,而在超时之前没有处理?

由于

1 个答案:

答案 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} };