发布于: 2023-7-12最后更新: 2023-10-9字数 00 分钟

QObject: Cannot create children for a parent that is in a different thread.

完整报错来源:
QObject: Cannot create children for a parent that is in a different thread. (Parent is QProcess(0xc56919faf0), parent's thread is QThread(0x211c9dbdef0), current thread is QThread(0x211ca103cb0)
原文的结构是:
AgentController类中有一个 SimulationEngine m_SimulationEngine类成员。 在AgentController类的构造函数中,
将这个类移动到新的线程中。 后续调用m_SimulationEngine成员函数的方法start,在这个方法中会调用m_SimulationEngine的成员变量QProcess mProcess; 使用mProcess.start创建一个新的进程。 这个时候会出现QObject: Cannot create children for a parent that is in a different thread. (Parent is QProcess(0xc56919faf0), parent's thread is QThread(0x211c9dbdef0), current thread is QThread(0x211ca103cb0)的报错。
Qt对象只能从创建它们的线程访问。这被称为线程关联。
在这种情况下,从与其父对象生存在不同线程的线程中创建QObject(或它的子类)会发生该错误。
因为当前AgentController类移动到了一个新线程中,但是m_SimulationEngine的成员变量mProcess是在原本的主线程中
解决方法:m_SimulationEngine这个成员和AgentController类一起移动到新线程中。实现这点只需要将m_SimulationEngine改为指针变量,在创建m_SimulationEngine的对象时设置父类为AgentController即可,AgentController移动线程时,所有子对象也会一起转移
但这个问题仍未结束,在下一点中继续。
扩展阅读:
关于线程安全、可重入、QObject的可重入:
  1. Qt 可重入和线程安全的理解 - 一杯清酒邀明月 - 博客园 (cnblogs.com)
  1. QThread、moveToThread用法详述_qthread movetothread_荆楚闲人的博客

复杂工作类的多线程问题

notion image
对于多线程的使用,一般情况下,如果只是希望把某个耗时的操作(函数)放入后台线程中运行,不影响主线程,那这个实现非常简单。特别是使用C++新特性,std::future和std::package_task等,非常简单。
但如果多线程是项目级别的,对很复杂的类进行多线程分配,这里的管理控制和生命周期维护就需要额外小心。
示例,这里项目核心控制类要移入一个独立的工作线程,与UI主线程分离。方式是在其构造函数将自己移入一个新线程中。
问题1:构造的时候,其是在主线程中被构造的,包括递归构造其成员变量,也都是在主线程中,那它们默认都属于主线程。构造函数,控制类自己移入了新线程,但是内部成员变量还在主线程。需要:将复杂类内部所有依赖对象递归移入新线程。
好消息是,QtmoveToThread支持将子对象递归移入新的线程,只要保证内部所有依赖都满足Qt对象树的组织方式即可(虽然这个假设不一定成立,不少对象可能是纯C++对象)。
问题2:目前新工作线程是在控制类内部,那就需要由控制类对其生命周期进行维护。考虑程序退出时析构对象。
假设控制器内部成员变量都使用Qt对象树维护自己的生命周期。那么析构的顺序就是,界面关闭->调用控制类的析构函数->逐个调用孩子成员变量的析构函数。但是对于控制类,其必须在退出前,在析构函数处理线程类的生命周期,退出线程。但是如果这个时候退出了线程,后续调用孩子的析构函数,孩子也都是依赖于工作线程的,但是工作线程已经退出了,那么就会导致崩溃。需要:独立于控制器以外进行线程生命周期的维护。
解决方案(目前):逐个手动管理成员变量的线程和生命周期

Qt事件系统梳理
Qt事件系统梳理
秋招复盘9.27:Qt C++知识梳理
秋招复盘9.27:Qt C++知识梳理