将对象移动到不同的线程

4
我的应用程序使用自定义序列化机制。
第一阶段:该机制在单独的线程上加载数据群集,创建所有适当的对象等等。
第二阶段:一旦它们全部被完全反序列化,它将它们交给主应用程序线程,并通过设置对象之间的连接等方式完成序列化。
这种机制是我框架的新添加 - 在此之前,我在主线程中反序列化所有内容。
我的问题是,在反序列化期间创建的某些对象是Qt对象(基本上是一堆小部件)。它们都记录了它们被创建的线程的ID。当“第二阶段”到来时,这些对象开始抱怨它们都不属于这个线程,因此不能发送任何信号等。
所以我在QObject上找到了一个叫做“moveToThread”的方法。然而,这个小东西并不是很有帮助,因为它执行检查并阻止从不同线程移动对象到当前线程(为什么会有这样的限制,我不知道)。
有人有想法如何解决这个问题吗?我可以保证对象只会在单独的线程上创建,并且从那时起,它们将全部生活和操作在主线程上。
谢谢, Paksas

5
在创建它们的线程中,将它们移动到主线程。示例代码:auto* p = new SomeQObject(); p.moveToThread(mainThread); - jalf
2
创建QT对象的成本与反序列化的成本相比如何?您能否将其反序列化为内存表示形式,然后在主线程中使用它来快速创建适当的QT对象? - Mankarse
“只需在创建它们的线程上将它们移动到主线程即可?”我尝试过这样做,但应用程序仍然抱怨一些子项仍由不同的线程拥有(此处讨论的小部件是 QGraphicsScene)。 - Piotr Trochim
与反序列化的成本相比,创建QT对象的成本有多高?您能否将其反序列化为内存表示形式,然后在主线程中快速创建适当的QT对象?问题在于序列化机制不知道它要创建什么类 - 但是它需要立即创建该类以便能够初始化其成员。因此,这不是一个可行的解决方案。 - Piotr Trochim
在次线程中拥有一个GraphicsScene是行不通的,如果GraphicsView在主线程中,因为这些类不是线程安全的,它们之间的交互将停止工作。 - Frank Osterfeld
2个回答

4

当涉及到多线程时,必须小心处理QObject的实例或任何子类的实例。

请参阅此页面以了解有关该主题的介绍。

假设您的应用程序由两个线程AB组成。假设线程A创建了一个QObject对象instance

instance被称为在线程A中运行。这意味着: - 它可以直接发送和接收来自线程A中所有其他对象的信号。 - 线程A中对myInstance方法的所有调用都是线程安全的。

另一方面,从另一个线程B访问instance: - 不是线程安全的(必须注意竞态条件)。 - 此外,由instance发出并连接到线程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的参数使用,我们需要将其注册到元框架中。您需要执行以下操作:
  • 在定义MyQObject的.cpp文件中添加Q_DECLARE_METATYPE(MyQObject*)
  • 在使用该机制之前(例如在main函数中)调用一次qRegisterMetaType<MyQObject*>();

0

只需将您的函数调用替换为QTimer::singleShot(int msec, const QObject *receiver, PointerToMemberFunction method),即可在另一个线程中运行插槽,因为根据文档,它会从目标线程执行插槽。

这样,您就不需要费心改变代码或者担心当前对象是否在同一线程中等问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接