c++20 标准库不提供 std::when_all,需依赖第三方库(如 libunifex)或手写实现;手写需处理 future 移动、异常捕获、类型统一等问题,而 libunifex 的 unifex::when_all 支持标签化异步聚合。

std::when_all 在 C++20 里根本不存在
标准库没有 std::when_all,这是很多人卡住的第一步。C++20 的 std::jthread 和协程(std::coroutine_handle)也没直接提供类似 when_all 的聚合原语。你看到的所谓“C++ when_all”基本来自第三方库(如 boost::asio、libunifex、cppcoro),或自己基于 std::future + 线程同步手写实现。
用 std::future + std::thread 手写 when_all 的关键点
如果你不想引入依赖,最可控的方式是封装一组 std::future,等全部就绪后收集结果。但要注意:标准 std::future 不支持超时等待多个对象,也不能主动取消;std::future::wait_for 是单个调用,没法“同时等全部”。
- 必须用循环轮询或条件变量 + 计数器来判断完成状态,不能靠单次系统调用
-
std::future移动后原对象变空,聚合前要确保不提前析构源 future - 异常传播需显式捕获:某个子任务抛异常,
get()会 rethrow,必须每个都 try-catch,否则程序终止 - 返回值类型必须统一(或用
std::variant/std::any包装),不能直接返回不同类型的 tuple
示例骨架:
template<typename... Ts>
auto when_all(std::future<Ts>&&... futures) {
std::vector<std::any> results;
(results.push_back(std::move(futures).get()), ...); // 顺序执行,非真并行等待
return results;
}
用 libunifex 实现带标签的异步聚合(推荐路径)
libunifex 是目前最贴近“标准未来”的无栈协程库,它提供了 unifex::when_all 和 unifex::tag_invoke 支持,能自然绑定任务与标签(比如用 std::string_view 或枚举作 key)。
立即学习“C++免费学习笔记(深入)”;
- 标签不是语法糖,而是通过自定义 type-erased sender 的
connect过程注入上下文 - 每个子任务用
unifex::then包一层,把结果和标签一起塞进 tuple:std::make_tuple("upload", std::move(result)) -
unifex::when_all返回的是 sender,必须用unifex::sync_wait或unifex::start_detached触发执行 - 注意内存生命周期:标签若为局部字符串字面量(如
"parse"),需确保在 sender 执行完前不销毁
片段示意:
auto task1 = unifex::then(some_async_op(), [](auto res) {
return std::make_tuple("fetch", std::move(res));
});
auto task2 = unifex::then(another_op(), [](auto res) {
return std::make_tuple("validate", std::move(res));
});
auto all = unifex::when_all(std::move(task1), std::move(task2));
auto result_vec = unifex::sync_wait(std::move(all)).value(); // vector of tuples
为什么别用 std::async 反复调用模拟 when_all
常见误区:用循环调 std::async 启一堆任务,再一个个 get()。这看似简单,但实际埋了三个坑:
- 默认启动策略是
std::launch::async | std::launch::deferred,可能部分任务被延迟执行,破坏“并发触发”语义 - 没做任何错误隔离:一个
get()抛异常,后续get()就不会执行,结果不全 - 无法感知整体超时 —— 每个
get()超时独立,没法“任一超时即整体失败”
真正需要标签化聚合时,异步模型本身就得支持上下文携带(比如 sender 中的 properties),而不是靠事后 map 索引补救。这点容易被忽略:标签不是加在结果上,是加在任务定义那一刻的。










