std::future不能直接链式调用,因其为一次性消费对象,get()/wait()后状态不可恢复,且标准C++11/14未提供then()等延续接口;强行串行调用退化为同步执行,丧失异步意义。

为什么 std::future 不能直接链式调用
因为 std::future 是一次性消费型对象,get() 或 wait() 后状态不可恢复,且不提供 then() 这类延续接口。强行在主线程阻塞等待再启动下一个任务,就退化成同步串行,完全失去异步意义。
常见错误是写成这样:
auto f1 = std::async([]{ return 42; });
int x = f1.get(); // 阻塞
auto f2 = std::async([x]{ return x * 2; }); // 等待 f1 完成后才构造 f2 —— 不是并发,也不是链式
- 这不是异步链式,只是“手动串行调度”,所有
std::async实际可能跑在同一线程(取决于策略) -
std::future没有 move-only 的延续绑定能力,无法把“f1完成 → 触发f2”这个关系注册进去 - C++11/14 标准库确实没提供
then,别硬凑;C++20 的std::jthread和std::stop_token也不解决链式问题
用 std::promise + 回调模拟链式 then
核心思路:让前一个异步任务的完成(set_value)自动触发后一个任务的启动,中间靠 std::promise 搭桥,靠 lambda 捕获控制流。
关键点在于——每个 then 返回一个新的 std::future,其就绪时机由上游 promise 决定:
立即学习“C++免费学习笔记(深入)”;
template<typename T, typename U>
auto then(std::future<T>&& prev, std::function<U(T)> func) {
auto p = std::make_shared<std::promise<U>>();
auto next_future = p->get_future();
// 在 prev 完成时执行 func,并 set_value 给 p
std::thread([p, func, prev = std::move(prev)]() mutable {
T val = prev.get(); // 此处阻塞,但在线程内,不影响主线程
p->set_value(func(val));
}).detach();
return next_future;
}
- 必须用
std::shared_ptr<:promise></:promise>,否则线程里访问的p可能在 lambda 执行前被销毁 -
std::thread(...).detach()是简化写法;生产环境建议用线程池或std::async(launch::async, ...)替代 - 这个
then不处理异常:若prev.get()抛异常,std::promise::set_exception必须显式调用,否则调用方next_future.get()会std::terminate
如何让链式任务真正“顺序执行但不阻塞主线程”
主线程只负责拼装链,不等待任何结果;最终的 future 可以在需要时 get(),也可以交给其他线程监听。
示例:三阶计算链 read_file → parse_json → validate
auto f = std::async([]{ return std::string{"{\"id\": 123}"}; });
<p>auto f2 = then(std::move(f), [](const std::string& s) -> nlohmann::json {
return nlohmann::json::parse(s);
});</p><p>auto f3 = then(std::move(f2), [](const nlohmann::json& j) -> bool {
return j.contains("id") && j["id"].is_number();
});</p><p>// 主线程继续干别的……
std::this_thread::sleep_for(10ms);</p><p>// 直到需要结果才 get(此时才可能阻塞)
bool valid = f3.get(); // 这里才真正等待整个链结束
- 每级
then都返回新future,类型可变(T → U → V),支持类型流转 - 若某环节耗时很长(如网络请求),它不会卡住后续
then的构造,只影响最终get()时间点 - 没有全局事件循环,不依赖第三方库(如 boost.asio),纯标准库组合
实际部署时最易忽略的资源与异常问题
上面的 then 实现看似简洁,但上线后容易因两个点崩溃:线程泄漏和异常穿透。
- 每次
then都detach()一个线程 → 若链很长或高频调用,线程数爆炸;应改用固定大小线程池 +post(task)方式调度 -
prev.get()若抛出异常(如 JSON 解析失败),当前实现直接终止程序;必须补全异常捕获并转发:p->set_exception(std::current_exception()) -
std::future不可复制,所有转移必须用std::move;漏写会导致编译失败或静默逻辑错误(比如重复get()) - 如果链中某个环节需要取消(如用户关闭页面),标准
std::future不支持 cancel;得自己加std::atomic_bool控制或换用std::stop_source(C++20)
链式异步不是语法糖,是控制流重构;每多一层 then,就多一分生命周期管理责任。


















