必须用moveToThread配合事件循环和QueuedConnection,Worker无父对象、不操作UI、内部QObject需同线程创建,线程启动后确保exec()运行,退出时用finished+deleteLater协同。

直接用 moveToThread 就行,但必须配合事件循环和正确的信号槽连接方式,否则槽函数根本不会进子线程执行。
Worker对象不能带父对象,否则moveToThread失效
这是最常翻车的第一步。如果你在构造 Worker 时传了 this(比如 new Worker(this)),那它会被强制绑定到主线程,后续调用 moveToThread 会静默失败——对象看起来“搬”过去了,但所有槽函数仍在主线程跑。
- Worker 必须是无父对象的:
new Worker(nullptr)或不传参(默认为nullptr) - UI控件(
QWidget及其子类)严禁调用moveToThread,Qt 会直接断言崩溃 - 如果 Worker 内部持有其他 QObject(如
QTimer、QSerialPort),它们也必须在子线程中创建或迁移,不能在主线程构造后挪过去
子线程必须运行事件循环,否则槽函数不触发
moveToThread 只改对象的线程亲和性,不自动启动线程。真正让槽函数在子线程执行的,是目标线程的事件循环(QEventLoop)。没它,信号发出去,槽函数永远收不到。
- 务必在
QThread::start()之后,确保线程已进入事件循环 —— 最稳妥做法是在线程启动后,由 Worker 自己发一个初始化信号,再在槽里做实际工作 - 不要在
run()里手动写exec();QThread默认自带事件循环,只要不重写run()就行 - 如果用了
QThreadPool或自定义线程池,得自己保证线程里调用了QEventLoop::exec()
跨线程信号槽必须用QueuedConnection
默认的 AutoConnection 在同一线程内是 DirectConnection,跨线程则退化为 QueuedConnection —— 听起来很智能,但实际容易误判。一旦信号发送时接收对象还没完成迁移(比如 moveToThread 和 connect 顺序反了),就可能变成 DirectConnection,导致槽函数仍在主线程执行。
立即学习“C++免费学习笔记(深入)”;
- 显式指定连接类型:
connect(sender, &Sender::sig, receiver, &Receiver::slot, Qt::QueuedConnection) - 参数类型必须注册为元对象类型(
qRegisterMetaType),否则 queued 连接会丢弃信号 - 避免在构造函数里连信号 —— 此时对象线程归属未定,
AutoConnection判定极易出错
安全退出要靠finished + deleteLater协同
线程退出不是调个 quit() 就完事。Worker 对象还在子线程里,如果直接 delete,会引发跨线程析构崩溃;如果只 quit() 不等 finished,线程可能已销毁,deleteLater 无法投递。
- 标准流程:Worker 发出完成信号 → 主线程调用
thread->quit()→ 连接thread->finished()到worker->deleteLater()和thread->deleteLater() - Worker 内部避免阻塞操作(如死循环、
QThread::sleep),否则事件循环卡住,finished永远发不出 - 若需协作式取消,用
QAtomicInteger或QMutex配合轮询检查,别依赖信号中断正在执行的槽
真正难的不是写几行 moveToThread,而是理解“线程亲和性”和“事件驱动”这两个底层约束。一旦漏掉事件循环、连错连接类型、或让对象带着父指针迁移,现象就是:代码看着全对,但槽函数就是不进子线程——这种问题往往要花半天时间盯着 QThread::currentThreadId() 打日志才能揪出来。


















