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