标准库 std::future 不支持 then,需用协程(co_await)或第三方库(如 libunifex)实现链式异步;手动封装需避免 get() 阻塞,推荐自定义 awaitable 或 sender 组合子。

用 std::future + then 模拟链式调用,但标准库不直接支持
标准 C++20 的 std::future 没有 then 方法,强行写 future.then(...) 会编译失败。这是最常卡住的第一步——很多人查文档后以为“C++ 原生支持链式异步”,结果发现只是提案(P0159)或实验性扩展(如 MSVC 的 std::experimental::future,已弃用)。
真正能落地的做法是:自己封装一层状态传递逻辑,或借助第三方库(如 libunifex、cppcoro),但若想零依赖,核心思路是让每个任务返回一个新的 std::future,由上层手动 wait() 或用线程池驱动后续任务。
- 别试图给
std::future加then成员函数——它被设计为只读、不可变 - 用
std::packaged_task包裹回调,把前一个future.get()结果传给下一个任务的输入参数 - 注意
get()是阻塞的,不能在主线程裸调;必须配合std::thread或线程池做非阻塞调度
用 std::coroutine 实现真·可挂起链式任务(C++20)
这才是目前最接近 “避免回调地狱” 的标准方案。关键不是语法糖,而是让异步逻辑看起来像同步代码,靠编译器把挂起点转成状态机,自动管理上下文切换。
典型结构是定义一个协程返回类型(如 task<int></int>),内部用 co_await 等待前序 std::future 或自定义 awaitable 对象,再用 co_return 把结果交给下一个环节。
立即学习“C++免费学习笔记(深入)”;
- 必须实现
operator co_await才能让std::future可被co_await—— 标准库没提供,得自己写轻量包装 - 不要在协程里直接
future.wait(),否则挂起失效,退化成同步阻塞 - 协程帧分配默认在栈上,大对象或长生命周期任务建议用自定义分配器(
promise_type::operator new)
task<int> fetch_then_process() {
auto data = co_await make_awaitable(http_get("https://api.example.com"));
co_return process(data) * 2;
}
为什么不用 std::async 直接连?
因为 std::async 返回的 std::future 不支持移动语义以外的组合操作,且启动策略(std::launch::async vs ::deferred)容易引发隐式阻塞。常见错误是写成:
auto f1 = std::async(...);
auto f2 = std::async([&]{ return f1.get() + 1; }); // 错!f1.get() 在构造 f2 时就执行了
这会导致顺序执行,完全失去并行意义,还可能死锁(尤其在单线程 executor 中)。
-
std::async适合“一次性发散任务”,不适合“收敛式链式依赖” - 多个
std::async之间没有调度关系,无法表达 A 完成后触发 B,B 再触发 C - 若强制用
std::async拼链,必须手动管理线程/队列,复杂度远超收益
libunifex 是目前最实用的生产级选择
它不依赖协程语法,基于 tag_invoke 和定制点(customization point objects)实现真正的惰性、可组合异步操作。所有操作符(then、let_value、on)都是无状态函数对象,返回新的 sender,真正做到了“描述即执行”。
和手写协程相比,它的优势在于:可取消、可调度到任意 executor(如线程池、GUI 线程)、天然支持错误传播(set_error),且编译期开销可控。
- 别用
unifex::sync_wait测试时图省事——它会阻塞当前线程,掩盖调度问题 - 注意
unifex::schedule返回的 sender 不会自动启动,必须显式unifex::start或用sync_wait驱动 - Windows 上 MSVC 对 unifex 的模板深度支持仍不稳定,建议用 Clang 15+ 或 GCC 12+
协程和 unifex 都要求你主动管理 executor 生命周期和错误路径,这点比 JavaScript 的 Promise 更“裸”。漏掉 co_await 的异常传播,或忘记对 unifex sender 做 set_done 处理,都会导致静默失败。









