将对象移动到不同的线程

时间:2012-12-20 10:29:08

标签: c++ multithreading qt

我的应用程序使用自定义序列化机制。

阶段1 )该机制在单独的线程上加载数据集,在那里创建所有适当的对象,等等。

阶段2 )一旦完全反序列化,它就会将它们交给主应用程序线程,并通过例如在对象之间建立连接等来完成序列化。

这个机制是我的框架的一个新增功能 - 在此之前我正在反序列化主线程上的所有内容。

我遇到的问题是在deserializatiuon期间创建的一些对象是Qt对象(基本上是一堆小部件)。 它们都记录了创建它们的线程的ID。当“第2阶段”到来时,这些对象开始抱怨它们都不属于这个线程,因此没有信号发送等。

所以我在QObject上找到了一个名为'moveToThread'的方法。虽然这个小bugger不是很有帮助,因为它执行检查并防止将对象从不同的线程移动到当前线程(为什么这是一个约束,我没有线索)。

有谁知道我怎么能这样做?我可以保证对象只在一个单独的线程上创建,从那时起它们都将在主线程上生存和操作。

谢谢, 帕克萨斯

1 个答案:

答案 0 :(得分:4)

在涉及多线程时,必须小心处理QObject的实例或QObject的任何子类实例。 请查看this page以了解该主题。

假设您的应用程序包含两个主题 A B 。 假设线程A 创建一个对象QObject 实例

实例据说生活在主题A 中。这意味着:    - 它可以直接向生活在线程A中的所有其他对象发送和接收信号。    - 从线程A对myInstance方法进行的所有调用都是线程安全的

另一方面,从其他主题B 访问实例:    - 不是线程安全的(你必须处理竞争条件)。    - 此外,实例发出并连接到线程B中对象插槽的信号不是直接的:通过复制所有方法参数并将所有方法放在要执行的事件中,将插槽执行推迟到稍后的时刻通过线程B事件队列。

鉴于此,您可以利用的解决方案如下。

SubClass是QObject的子类。

class SubClass : public QObject{
Q_OBJECT
[...]
}

线程A将运行以下方法来反序列化并使用SubClass实例填充内存

void methodA(){  /this method is executed by thread A

 QThread* threadB; //a reference to the QThread related to thread B
 [...]
 MyQObject* instance = someFactoryMethod();

 //we push the ownership of instance from thrad A to thread B
 instance->moveToThread( threadB ); 

 //do something else

}

请注意,如果线程A需要在实例上执行其他操作,这可能还不够。特别是可能会发生线程A将触发MyQObject中定义的一些信号。这是不允许的,因为现在线程A不再是实例的所有者了。

在这种情况下,您需要推迟此类操作并请求线程B执行它。这是通过使用 QMetaObject :: invokeLater 方法实现的。

InvokeLater允许您调用特定的插槽,要求线程B执行它。

假设ClassInB是一个在线程B中使用其实例的类

class ClassInB : public QObject{
 Q_OBJECT

public:
    [...]
slots:
  void complexOperation(MyQObject* o){
    [...]
    emitSomeSignal();
  }
signals:

  void someSignal();
}

实例移动到线程B后,我们需要在生活在线程B中的ClassInB实例上执行 complexOperation(),而实际上它也会发出someSignal()

void methodA(){  //this method is executed by thread A
  QThread* threadB; //a reference to the QThread related to thread B
  ClassInB* instanceOnB;   //a reference to a ClassInB instance living in thread B
  [...]

   MyQObject* instance = someFactoryMethod();

   //we push the ownership of instance from thread A to thread B
   instance->moveToThread( threadB ); 

   //we ask thread B to perform some operation related to instance
   QMetaObject::invokeLater(instanceOnB, "complexOperation", Q_ARG(MyQObject*, instance) );

}

为了能够将MyQObject *用作 invokeLater 的参数,我们需要将其注册到Meta框架。你需要:

  • 在定义MyQObject的.cpp中添加Q_DECLARE_METATYPE(MyQObject*)
  • 在使用机制之前调用一次(例如在主要内容中),qRegisterMetaType<MyQObject*>();