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类的构造函数中,
1
2
3
m_testThread = new QThread;
this->moveToThread(m_testThread);
m_testThread->start();
将这个类移动到新的线程中。 后续调用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
2
3
4
5
6
7
8
9
AgentController::AgentController()
: m_SimulationEngine(new SimulationEngine()) // 设置控制器为父类,后面movetoThread一起移动线程
, pDDSManager(nullptr)
{
// 线程池TODO
m_testThread = new QThread;
this->moveToThread(m_testThread);
}
问题1:构造的时候,其是在主线程中被构造的,包括递归构造其成员变量,也都是在主线程中,那它们默认都属于主线程。构造函数,控制类自己移入了新线程,但是内部成员变量还在主线程。需要:将复杂类内部所有依赖对象递归移入新线程。
好消息是,Qt的moveToThread支持将子对象递归移入新的线程,只要保证内部所有依赖都满足Qt对象树的组织方式即可(虽然这个假设不一定成立,不少对象可能是纯C++对象)。
问题2:目前新工作线程是在控制类内部,那就需要由控制类对其生命周期进行维护。考虑程序退出时析构对象。
1
2
3
4
5
6
7
8
AgentController::~AgentController()
{
// 线程池TODO
m_testThread->quit();
m_testThread->wait();
m_testThread->deleteLater();
}
假设控制器内部成员变量都使用Qt对象树维护自己的生命周期。那么析构的顺序就是,界面关闭->调用控制类的析构函数->逐个调用孩子成员变量的析构函数。但是对于控制类,其必须在退出前,在析构函数处理线程类的生命周期,退出线程。但是如果这个时候退出了线程,后续调用孩子的析构函数,孩子也都是依赖于工作线程的,但是工作线程已经退出了,那么就会导致崩溃。需要:独立于控制器以外进行线程生命周期的维护。
解决方案(目前):逐个手动管理成员变量的线程和生命周期。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
AgentController::AgentController()
: m_SimulationEngine(new SimulationEngine()) // 设置控制器为父类,后面movetoThread一起移动线程
, pDDSManager(nullptr)
{
// 线程池TODO
m_testThread = new QThread;
this->moveToThread(m_testThread);
m_SimulationEngine->moveToThread(m_testThread);
connect(m_testThread, &QThread::finished, m_SimulationEngine, &QObject::deleteLater);
m_testThread->start();
init();
}
AgentController::~AgentController()
{
// 手动管理成员变量的生命周期
m_SimulationEngine->deleteLater();
// 线程池TODO
m_testThread->quit();
m_testThread->wait();
m_testThread->deleteLater();
// int nId = (int)m_testThread->currentThreadId();
// m_testThread->killTimer(nId);
}