这段代码的完整意图
void execTask(std::function<void()> task)
{
// 如果当前就在对象线程里---1
if(QThread::currentThread() == T::thread())
{
task(); // 直接执行
}
else
{ // 不在对象线程 → 投递到对象线程执行
QMetaObject::invokeMethod(this, [=]() { task(); }, Qt::QueuedConnection);
}
}
为什么要这么写?(Qt 核心规则###
Qt 有一条铁律:
QObject 对象的所有非 const 成员函数,必须在它所属的线程中调用!
否则会:
信号槽失效
界面卡死
程序崩溃
多线程竞争、
数据错乱
所以这段代码的目的是:
安全地执行任务,确保任务一定在对象归属线程中运行
作用:无论从哪个线程调用 execTask,task 都会安全地在对象归属线程执行。
if(QThread::currentThread() == T::thread())
意思:判断当前代码是否运行在【这个对象所属的线程】中。
常用跨线程调用方式
先记住一句话
所有跨线程执行的核心只有一个:
把 “要执行的函数” 投递到目标线程的事件循环里。
下面这些方法本质都是干这件事。
一、信号槽(最常用、最简单)
这是 Qt 最推荐、最安全、代码最干净的方式。
// 在对象线程的类里定义一个槽函数
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork() {
// 这里一定运行在对象线程
}
};
// 跨线程调用
emit signalToWork(); // 连接时用 Qt::QueuedConnection
关键点
只要连接方式是 Qt::QueuedConnection(跨线程自动默认)
槽函数百分百在接收者所属线程执行
不需要手动判断线程
优点
最简单
最安全
支持参数传递
不会有野指针风险
二、QTimer::singleShot (0, ...)(超实用技巧)
这是 Qt 里隐藏的神技,很多老程序员都用它。
用法
cpp
运行
QTimer::singleShot(0, this,[this]() {
// 这里一定运行在 this 所属线程
});
原理
delay=0 不代表立刻执行
而是把任务压入事件队列尾部
事件循环在对象线程里跑 → 函数就在对象线程执行
优点
一行代码
不需要信号槽
不需要 invokeMethod
支持 Lambda
三、子类化 QThread + run () + exec ()(传统方式)
这是 Qt 最经典的线程写法,但不推荐直接在 run 里写业务。
用法
cpp
运行
class MyThread : public QThread
{
void run() override {
// 这里是新线程
Worker worker; // 该对象属于新线程
exec(); // 开启事件循环
}
};
特点
run() 内部就是新线程
你在 run() 里创建的对象都属于该线程
必须调用 exec() 才能让信号槽 /postEvent 生效
缺点
不灵活
不适合频繁切换任务
四、moveToThread + 自定义触发(最标准架构)
这是 Qt 官方最推荐的多线程架构。
用法
cpp
运行
QThread* thread = new QThread;
Worker* worker = new Worker;
worker->moveToThread(thread); // 把对象丢进线程
thread->start();
// 之后所有 worker 的槽、事件、invokeMethod 都在新线程执行
优点
线程与逻辑解耦
最稳定
适合长期后台任务
五、QtConcurrent::run ()(简单异步,不控制线程)
适合一次性后台任务,不关心具体线程。
用法
cpp
运行
QtConcurrent::run( {
// 这里在线程池里运行
});
特点
自动使用线程池
不需要手动创建 QThread
不能精确控制哪个线程
缺点
不能和 QObject 事件循环配合
不能安全更新 UI
总结:跨线程执行的 6 种方法
我给你做一张最实用速查表:
- 信号槽(Qt::QueuedConnection) → 最常用、最安全
- QMetaObject::invokeMethod → 最灵活、支持 Lambda
- postEvent → 底层机制,适合自定义事件
- QTimer::singleShot(0, ...) → 最简单的 Lambda 投递
- moveToThread + 槽函数 → 官方标准架构
- QtConcurrent::run → 一次性后台任务
所有 Qt 里真正能 “切换线程执行代码” 的方法一次性讲全,包含冷门但合法的方法,但只讲能安全用在项目里的,不玩花活。
我会分成:标准方法(你已知) + 冷门正统方法(你未知) + 绝对不能用的方法(避坑)
- QObject::event () + 自定义事件(底层正统)
这是 Qt 所有线程切换的底层源头,postEvent 就是基于它。
你可以自己定义事件,发出去后,一定会在对象线程里执行。
class MyEvent : public QEvent
{
public:
std::function<void()> func;
MyEvent(std::function<void()> f) : QEvent(QEvent::User), func(std::move(f)) {}
};
// 在 TaskExecuter 里重写 event
bool event(QEvent e) override
{
if (e->type() == QEvent::User) {
auto me = static_cast<MyEvent>(e);
me->func();
return true;
}
return T::event(e);
}
// 切换线程
void runInThread(std::function<void()> f)
{
qApp->postEvent(this, new MyEvent(std::move(f)));
}
✅ 优点:最底层、最稳定
✅ 完全可控
2. Qt::BlockingQueuedConnection(阻塞式跨线程调用)
这是同步版跨线程调用:
发出去 → 等待对方线程执行完 → 再回来。
QMetaObject::invokeMethod(this, task, Qt::BlockingQueuedConnection);
适用场景:
需要等待线程执行结果
必须同步获取返回值
⚠ 注意:不能在目标线程已死锁时调用。
3. QApplication::postEvent + 自定义事件(全局投递)
和第 1 种一样,只是通过全局对象投递。
qApp->postEvent(this, new MyEvent([]{ /* 运行在对象线程 */ }));
4. QThread::exec () 事件循环 + 自定义队列任务
你自己维护一个任务队列,然后用事件唤醒执行。
// 线程里跑一个循环
while (!quit) {
processTasks(); // 执行任务
exec(); // 等待事件
}
这是手写线程工作器,Qt 源码大量使用。
5. QFuture + QFutureWatcher(异步结果回调)
适合需要返回值的跨线程执行。
QFuture
QFutureWatcher
connect(&watcher, &QFutureWatcher
// 这里运行在 this 线程
int res = future.result();
});
watcher.setFuture(future);
✅ 优点:支持返回值、线程安全、自动切换
三、不推荐但理论上可以切换线程的方法(尽量别用)
- 直接调用 QObject 成员(极危险)
obj->mySlot(); // 跨线程直接调用 → 崩溃、数据混乱
❌ 禁止!
2. std::thread + 手动同步(不兼容 Qt 事件)
cpp
运行
std::thread([obj]{ obj->doWork(); }).detach();
❌ 不进入 Qt 事件循环,QObject 不安全
四、最终总结:Qt 里所有合法切换线程的方法
我给你一个最全清单,你以后遇到任何 Qt 线程问题都能对照:
信号槽(QueuedConnection)
QMetaObject::invokeMethod
QTimer::singleShot(0, ...)
QCoreApplication::postEvent
自定义 QEvent + event () 重写
moveToThread + 事件循环
BlockingQueuedConnection(同步等待)
QFutureWatcher(异步结果回调)
手写任务队列 + 事件唤醒
以上 9 种是 Qt 世界里全部真正安全、可用、官方支持的线程切换方法。
五、如果你要最强大、最通用、最简洁的写法
我直接给你最终版万能线程切换函数,任何 Qt 项目都能直接用:
void runInObjectThread(std::function<void()> task)
{
if (QThread::currentThread() == this->thread()) {
task();
} else {
QMetaObject::invokeMethod(this, std::move(task), Qt::QueuedConnection);
}
}
这行是Qt 工业界标准万能写法,覆盖 100% 场景。
最终总结
你问的是:
除了你之前讲的,还有哪些?
我给你的新增真正可用方法是:
自定义事件 + postEvent(底层正统)
BlockingQueuedConnection(同步跨线程)
QFuture + QFutureWatcher(带返回值)
手写任务队列 + 事件唤醒
这 4 个是标准、安全、官方,且之前没讲过的。
浙公网安备 33010602011771号