QMetaObject::invokeMethod -processNext 如何在工作线程(m_workerThread)中执行
QMetaObject::invokeMethod -processNext 如何在工作线程(m_workerThread)中执行
这段代码中,processNext 能够在工作线程(m_workerThread)中执行,核心原因在于 Qt 的事件循环(Event Loop)和线程亲和性(Thread Affinity)机制。 具体的关联建立过程可以分为以下几个关键步骤: 1. 改变线程亲和性 (Thread Affinity) cpp 编辑 m_workerThread = new QThread(this); this->moveToThread(m_workerThread); 在构造函数中,this->moveToThread(m_workerThread); 将当前 QThreadTask 对象的线程亲和性转移到了 m_workerThread。 在 Qt 中,一个 QObject 的事件处理函数(Slot 或 invokeMethod 触发的函数)默认会在该对象所属的线程中执行。 2. 启动事件循环 (Event Loop) cpp 编辑 m_workerThread->start(); 当调用 start() 时,m_workerThread 开始运行,并自动进入其内部的事件循环(相当于调用了 exec())。此时,这个线程具备了接收和处理事件队列中消息的能力。 3. 通过队列连接投递事件 (Queued Connection) cpp 编辑 QMetaObject::invokeMethod(this, &QThreadTask::processNext, Qt::QueuedConnection); 当外部调用 addTask 时,使用了 Qt::QueuedConnection 来调用 processNext。 如果是跨线程调用(比如从主线程调用 addTask):invokeMethod 不会直接执行 processNext,而是会将一个“执行 processNext 的事件”打包,投递到 this 对象所属线程(即 m_workerThread)的事件队列中。 如果是同线程调用:虽然 QueuedConnection 也会投递到事件队列,但会在当前事件循环的下一次迭代中执行。 4. 事件循环消费事件 m_workerThread 的事件循环从队列中取出这个“执行 processNext”的事件,然后调用 processNext()。因为事件循环是在 m_workerThread 中运行的,所以 processNext() 自然就运行在了 m_workerThread 中。 5. 递归调度形成闭环 在 processNext() 的末尾: cpp 编辑 QMetaObject::invokeMethod(this, &QThreadTask::processNext, Qt::QueuedConnection); 它再次向 m_workerThread 的事件队列投递了一个执行 processNext 的事件。这就形成了一个自循环:只要队列里有任务,processNext 就会不断地被事件循环调度执行;如果队列空了,它就不再投递,循环自然停止,等待下一次 addTask 来唤醒。 ⚠️ 代码中的潜在隐患与改进建议 虽然上述机制解释了“为什么能跑”,但这段代码在实际工程中存在几个严重的隐患: 构造函数中 moveToThread 是危险操作: 在构造函数中调用 this->moveToThread() 是不推荐的。如果此时有其他对象已经 connect 了这个对象的信号,或者在父对象的构造过程中发生异常,可能会导致未定义行为。通常建议提供一个单独的 init() 或 start() 方法来进行 moveToThread。 finished 信号连接 deleteLater 的逻辑冲突: cpp 编辑 connect(m_workerThread, &QThread::finished, this, &QObject::deleteLater); 这行代码的意思是:当工作线程结束时,销毁 QThreadTask 对象。 但是,QThreadTask 是在 m_workerThread 中执行任务的。如果线程结束了,QThreadTask 被销毁,那么下次再调用 addTask 时,this 指针已经悬空(Dangling Pointer),程序会直接崩溃。通常 deleteLater 应该用在工作线程执行完毕且不需要复用的场景,或者由外部管理器来销毁。 stop() 的线程安全问题: stop() 中直接访问 m_workerThread->isRunning() 和 quit()。如果 stop() 是从主线程调用的,而 m_workerThread 正在执行耗时任务,quit() 只是告诉事件循环退出,它不会中断正在执行的 task()。如果 task() 是一个死循环或极耗时的操作,wait(3000) 超时后,线程依然无法结束,强行销毁会导致崩溃。通常需要配合 QAtomicInt 或 std::atomic<bool> 来做取消标志。 总结 processNext 之所以能在工作线程执行,完全依赖于 moveToThread 改变了对象归属 + QueuedConnection 将执行请求打包成事件 + 工作线程的事件循环消费该事件 这一套 Qt 核心机制
欢迎讨论,相互学习。
cdtxw@foxmail.com

浙公网安备 33010602011771号