std::promise与std::future必须一对一配对,通过get_future()显式关联共享状态;每个promise仅能调用一次set_value()或set_exception(),链式传递需为每阶段创建独立promise,由上游future就绪后触发下游promise设值。

std::promise 和 std::future 怎么配对才能链式传递结果
不能靠多次 get() 或反复 set_value(),std::promise 只能调用一次 set_value()(或 set_exception()),否则抛出 std::future_error: promise already satisfied。链式调用的本质是:前一个任务完成时,触发下一个任务的 promise 设置,而不是复用同一个 promise。
实操建议:
- 每个异步阶段独立持有自己的
std::promise<t></t>,用于向下游提供std::future<t></t> - 上游任务(如
std::async返回的std::future<u></u>)用then类逻辑,在其wait_for(0)或通过线程轮询/条件变量感知就绪后,调用下游promise.set_value(...) - 更稳妥的做法是把下游
promise捕获进 lambda,作为参数传给上游的回调(比如用std::thread+detach模拟简单 then,但注意生命周期)
为什么不能直接 return std::future 从 async 函数里“链起来”
std::future 不可拷贝,只能移动;且一旦被 get() 或 wait() 消费过,再次调用会抛 std::future_error: no state。所谓“链式”,不是把 future 当值返回,而是让前序 future 的就绪动作驱动后续计算的启动。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 函数签名写成
std::future<int> step1() { return std::async(...); }</int>,然后auto f = step1().then(...)——then根本不存在,std::future没有该成员 - 手动写
auto f1 = step1(); auto f2 = step2(f1.get());—— 这是阻塞式串行,不是异步链式;且f1.get()后f1失效,无法再用于状态检查
正确思路:用包装类型(比如 Task<t></t>)封装 std::future<t></t> 和延续逻辑,重载 operator>>= 或提供 and_then 方法,内部管理新 promise 的创建与触发。
手写 Task 链式调度的关键三步
不依赖第三方库(如 folly、cppcoro),纯 C++17 可实现轻量级链式任务封装。核心不在“怎么跑”,而在“怎么把 A 的输出安全喂给 B 的输入,并保证 B 在 A 就绪后才启动”。
实操建议:
-
构造时只存
std::future<t></t>,不立即执行:避免意外触发求值;延迟到start()或首次and_then时才真正 launch -
and_then 的 lambda 必须接收
T&&(非 const rvalue 引用):防止用户误在 lambda 里对结果做耗时拷贝;同时允许 move 走大对象 -
下游 promise 的 lifetime 必须由当前 Task 管理:典型做法是把
std::promise<u></u>和 lambda 一起打包进std::shared_ptr,传给新线程,避免栈变量提前析构
示例片段(简化版):
template<typename T>
struct Task {
std::future<T> fut_;
template<typename F>
auto and_then(F&& f) -> Task<std::invoke_result_t<F, T&&>> {
auto p = std::make_shared<std::promise<std::invoke_result_t<F, T&&>>>();
auto ret_fut = p->get_future();
std::thread([p, f = std::forward<F>(f), fut = std::move(fut_)]() mutable {
try {
auto&& val = fut.get(); // 阻塞在此,但在线程内
p->set_value(f(std::move(val)));
} catch(...) { p->set_exception(std::current_exception()); }
}).detach();
return {std::move(ret_fut)};
}
};
std::async 默认 launch::deferred 会导致链式失效
如果没显式指定策略,std::async 在某些编译器(如 GCC libstdc++)下可能默认用 launch::deferred,即调用 get() 前不真正执行。这会让链式中的“等待上游完成”变成同步阻塞,失去并发意义,甚至死锁(比如在主线程调 get(),而下游又试图在同一线程发新 async)。
必须显式指定:
-
std::async(std::launch::async, ...)强制后台线程执行 - 或统一用
std::thread+std::promise手动控制,更可控 - 注意:Windows MSVC 的默认策略是
async,但跨平台代码绝不能依赖此行为
容易被忽略的一点:即使用了 launch::async,如果链式中某步抛异常且未被 set_exception 捕获,下游 future.get() 会 rethrow,但调用栈已丢失原始位置 —— 建议所有 set_value 路径外都包一层 try/catch(...) 并调用 p->set_exception(...)。


















