std::async 不提供自动回调机制,必须主动调用 get() 或 wait() 获取结果;默认延迟策略下不调用则函数不执行,异常也需 get() 才抛出;可用轮询或 std::packaged_task 模拟回调,C++20 协程需封装 awaitable。

std::async 启动异步任务后,回调不会自动执行——得自己安排
很多人以为 std::async 能像 JavaScript 的 then() 那样链式注册回调,其实它只返回一个 std::future,本身不提供回调机制。所谓“自动执行回调”,本质是你得在某个时机主动调用 get() 或 wait(),再手动触发后续逻辑。
常见错误是把 std::async 当成“后台线程+回调注册器”,结果主线程没等、没取值、也没检查异常,任务悄悄失败或卡死。
-
std::async默认策略(std::launch::deferred)下,不调get()就根本不执行函数 - 即使用了
std::launch::async,任务完成后std::future也不通知你——它只会阻塞get(),不会调你的回调函数 - 如果异步函数抛异常,异常被封装进
std::future,不调get()就永远看不到
想“自动执行回调”,得靠 future 的 wait_for + get 组合轮询(慎用)
没有内置回调不等于不能模拟。一种轻量做法是起一个短周期轮询线程,检查 std::future::wait_for(std::chrono::milliseconds(0)) 是否返回 std::future_status::ready,然后调 get() 并执行回调。
但注意:轮询浪费 CPU,且不是真正“事件驱动”。适合低频、调试或嵌入式受限场景,生产环境建议换方案。
- 轮询前先确认
std::future是 valid 的(构造失败或已 move 过会 invalid) -
wait_for返回std::future_status::timeout时别直接 continue,加点 sleep(比如std::this_thread::sleep_for(1ms)) - 一旦
wait_for返回ready,必须立刻get()——否则下次get()会抛std::future_error(error code:no_state)
更靠谱的做法:用 std::thread + std::packaged_task 手动调度回调
如果你真需要“任务完成即调回调”,std::packaged_task 搭配 std::thread 更可控。它能把任意可调用对象包装成能绑定 std::future 的任务,并在执行完自动就绪——这时你在另一端用 get() 或另起线程监听都行。
关键点在于:回调逻辑写在 std::packaged_task 包裹的 lambda 里,而不是试图“挂”在 future 上。
auto task = std::make_shared<:packaged_task>>(
[]() {
// 实际工作
int result = heavy_calc();
// 完成后立即执行回调(同步)
on_finished(result);
}
);
std::thread t{[task]() { (*task)(); }};
t.detach(); // 或管理生命周期
</:packaged_task>
- 别直接传裸 lambda 给
std::thread然后指望 future 回调——lambda 没绑定 future,无法通知 - 用
std::shared_ptr包住std::packaged_task是为了跨线程安全访问(尤其 detach 后) - 如果回调要改 UI 或进特定线程,得额外加消息队列或 post 到目标 event loop,C++ 标准库不负责这事
现代替代方案:C++20 协程 + awaitable future(避免手写状态机)
C++20 引入协程后,可以用 co_await 等待 std::future(需自行实现 await_ready/await_suspend),让“等待+回调”看起来像同步代码。但这不是开箱即用的——标准 std::future 不满足 awaitable 要求,得封装一层。
实际项目中,若已引入 libunifex 或 cppcoro,它们提供了 task<t></t> 和 when_all 等原语,比手搓 std::async + 轮询干净得多。
- 别尝试给
std::future直接加operator co_await全局重载——它没await_suspend接口,编译不过 - 协程栈空间默认较小(几 KB),递归深或局部变量大时容易 overflow,得显式配置
stackless或调大 - 协程对象本身不是线程安全的,多个线程同时
co_await同一个实例会出问题
最常被忽略的一点:无论用哪种方式,“回调执行上下文”都不是自动继承的。比如你在 GUI 主线程启动 async,回调却在 worker 线程执行——更新控件会崩。这和语言无关,是并发模型的基本约束,得靠程序员显式 dispatch。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











