主要矛盾:timer创建是在主线程并依赖this析构,然后this被间接移动到了子线程,所以timer析构就与创建就不在同一线程导致报错。
Qt 把某对象本身 移到 子线程,它的成员对象也随对象移动。但是子 QObjects不会自动改变所属线程,它们的 thread() 仍然是创建它们时的线程,QTimer 是 QObject 的子类,所以也不会随之移动线程。
-
QObject 本身移动
-
someObject->moveToThread(targetThread)只移动someObject自身 -
someObject的成员变量(如果是普通对象或非 QObject)随对象生命周期移动(只是 C++ 内存布局,和线程无关)
-
-
QObject 的子对象
-
已经存在的子 QObject(例如
QObject* child = new QObject(parent))不会随父对象移动线程 -
它们的
thread()保持创建时的线程
-
-
QTimer 特例
-
QTimer是QObject子类 -
如果
QTimer在父对象构造时创建,它的所属线程 = 父对象当时所在线程 -
当父对象被
💡 结论:moveToThread移动,QTimer的thread()不变 → 跨线程 stop/delete 就会出问题。-
如果父对象被移动到新线程,想让已有 QTimer 在新线程工作,必须 手动调用
timer->moveToThread(targetThread),或者在移动父对象之后再创建 QTimer。
-
-
1️⃣ 对象与线程关系
-
DispatchScheduler
-
创建在主线程
-
DispatchScheduler成员
_polling也在主线程 -
_polling内部有 成员_communicator,也随之在主线程
-
-
_communicator中创建QTimer
QTimer* timer = new QTimer(this); // this = _communicator
-
所属线程 =
_communicator所在线程 = 主线程
-
调用顺序
dispatchScheduler.init(); // 调用 _polling.allStatusPolling() → 创建 QTimer
dispatchScheduler.start(); // moveToThread(&_schedulerThread)
-
QTimer 已经在主线程创建
-
DispatchScheduler 后面移动到子线程 →
_polling和_communicator成员随对象移动 -
QTimer 线程不随对象移动 → 所属线程仍然是主线程
2️⃣ 出现的问题
-
QTimer 依赖父对象析构 (
this = _communicator) -
当 DispatchScheduler 析构(在子线程)时,会析构
_polling和_communicator -
跨线程析构 QTimer → 报错:
QObject::killTimer: Timers cannot be stopped from another thread
3️⃣ Lambda 与 TaskRunner 线程
-
QTimer timeout 信号在 主线程触发
-
lambda 提交 TaskRunner 到
_pollingThreadPool→ 在线程池线程执行 -
callback/statusReceived 也在线程池线程 → 发信号到 AgvStatusManager,Qt 自动使用 QueuedConnection
-
✅ 这部分线程安全
4️⃣ 核心问题总结
| 条件 | 风险 |
|---|---|
| QTimer 创建在主线程 | 所属线程 = 主线程 |
_communicator 被移动到子线程 | 对象析构时 QTimer 依赖父对象析构,父对象被移动到了子线程 |
| DispatchScheduler 析构 | QTimer stop/delete 在 子线程 → 跨线程 killTimer 报错 |
5️⃣ 安全解决方案
方法 A:先移动对象再创建定时器
dispatchScheduler.start(); // moveToThread(&_schedulerThread)
_polling.allStatusPolling(2000); // 在调度线程创建 QTimer
-
timer 所属线程 = 调度线程 → 析构安全
方法 B:timer 独立线程,不依赖父对象
QTimer* timer = new QTimer(nullptr);
timer->moveToThread(_workerThread);
connect(timer, &QTimer::timeout, lambda);
timer->start(intervalMs);
-
不依赖父对象,析构安全
-
lambda 提交任务到线程池 → 线程安全
方法 C:移动已创建 timer
timer->moveToThread(&_schedulerThread);
-
确保 start/stop 在目标线程执行
6️⃣ 析构安全示例(AgvCommunicator)
AgvCommunicator::~AgvCommunicator()
{
// 停掉所有定时器
for (auto timer : _pollingTimers) {
if (timer) {
timer->stop();
timer->deleteLater();
}
}
_pollingTimers.clear();
// 安全退出轮询线程
if (_pollingThread->isRunning()) {
_pollingThread->quit();
_pollingThread->wait();
}
// 不需要手动 delete _pollingThread(parent=this)
}
7️⃣ 线程关系图(增强版)
主线程
│
├─ DispatchScheduler (创建)
│ └─ _polling (成员)
│ └─ _communicator (成员)
│ └─ QTimer (创建时在主线程)
│
│ QTimer timeout ──> Lambda ──> _pollingThreadPool(TaskRunner)
│
子线程 (_schedulerThread)
│
└─ DispatchScheduler 被 moveToThread(&_schedulerThread)
└─ _polling / _communicator 随对象移动
└─ QTimer 所在线程仍是主线程
说明
-
QTimer 所属线程:创建时线程(主线程),不会随对象移动。
-
析构问题:DispatchScheduler 在子线程析构 →
_communicator析构 → 尝试 stop/delete QTimer → 跨线程报错。 -
Lambda 执行线程:timeout 信号触发 lambda → 提交 TaskRunner 到
_pollingThreadPool→ 线程池线程执行 → 发信号回 AgvStatusManager(自动使用 QueuedConnection)。 -
解决方案:
-
移动对象后再创建 QTimer,或者
-
定时器独立线程,moveToThread 到专门的
_pollingThread
-
✅ 总结
-
_polling本身没有 timer,没问题 -
真正线程问题在
_communicator内部 QTimer -
解决方法:
-
移动对象后再创建 QTimer,或者
-
timer 不依赖父对象并手动 moveToThread
-
-
Lambda 捕获局部变量或 QSharedPointer,避免析构访问悬空对象
1398

被折叠的 条评论
为什么被折叠?



