std::async不能链式调用因C++17无.then();需封装可组合异步句柄,用shared_ptr+promise+回调实现非阻塞链式执行,并确保异常透传与生命周期安全。

为什么 std::async 串成链会崩?
直接用 std::async 连续调用(比如 std::async(...).then(...))会编译失败——C++17 标准库根本没有 .then() 成员。这是最常踩的坑:误把 std::future 当成 JS Promise 或 Rust Future 来用。
真正能链式执行的前提是自己封装一个可组合的异步句柄,核心是让前序任务的 std::future 能触发后序任务的调度,且不阻塞线程。
- 别在主线程里对
std::future::get()做链式等待,这等于退化成同步调用 - 避免裸用
std::packaged_task+std::thread手动管理生命周期,容易悬垂或提前析构 - 必须显式处理异常传递:
std::future::wait_for不会抛出前序任务的异常,得用get()显式触发
用 std::shared_ptr + std::function 构建可链式挂载的任务节点
链式模型的本质不是“返回 future”,而是“注册回调”。每个任务节点持有上一节点完成后的执行逻辑,并把自身结果交给下一节点——靠共享状态和延迟调用实现。
关键结构体示意:
立即学习“C++免费学习笔记(深入)”;
struct TaskNode {
std::shared_ptr<std::promise<int>> promise_;
std::function<void(int)> on_complete_;
std::function<int()> work_;
};
-
work_是实际要异步执行的函数(无参、返回int,可泛化为模板) -
on_complete_是前序结果到来时触发的回调,参数即前序promise_.get_future().get()的值 -
promise_供本节点完成后设置结果,下游靠promise_.get_future()监听
这样每个节点只依赖上一节点的 std::future 就能启动,无需等待,也不需要语言级协程支持。
如何安全地把 std::future 转成可链式消费的句柄?
不能直接传 std::future 到另一个线程里 move(它不可拷贝,move 后原对象失效),但可以把它包装进 std::shared_ptr 并配合原子状态控制消费时机。
推荐做法是:用 std::shared_ptr<:future>></:future> + 线程池回调,例如:
template<typename T, typename F>
auto then(std::shared_ptr<std::future<T>> fut, F&& callback)
-> std::shared_ptr<std::future<decltype(callback(std::declval<T>()))>> {
auto p = std::make_shared<std::promise<decltype(callback(std::declval<T>()))>>();
auto result_fut = std::make_shared<std::future<decltype(callback(std::declval<T>()))>>(p->get_future());
// 在线程池中等待 fut 完成并触发 callback
thread_pool.enqueue([fut, p, cb = std::forward<F>(callback)]() mutable {
try {
auto val = fut->get(); // 这里可能抛异常
p->set_value(cb(val));
} catch (...) {
p->set_exception(std::current_exception());
}
});
return result_fut;
}
- 每次
then都返回新的std::shared_ptr<:future>></:future>,避免裸 future 生命周期失控 - 异常必须用
set_exception透传,否则下游get()会 terminate - 不要在
then回调里再调用get()—— 这又变成同步阻塞了
线程池调度器里怎么避免 std::future::wait 空转耗 CPU?
常见错误是在线程池 worker 中轮询 fut.wait_for(1ms) == std::future_status::ready,这本质是忙等,完全违背异步初衷。
正确做法是:让任务提交者不主动 wait,而是由系统通知。C++20 前只能靠条件变量 + 状态标记模拟,例如:
struct AsyncFuture {
std::mutex mtx_;
std::condition_variable cv_;
bool ready_ = false;
std::optional<int> value_;
std::exception_ptr ex_;
void set_value(int v) {
std::lock_guard lk(mtx_);
value_ = v;
ready_ = true;
cv_.notify_all();
}
int get() {
std::unique_lock lk(mtx_);
cv_.wait(lk, [&] { return ready_; });
if (ex_) std::rethrow_exception(ex_);
return value_.value();
}
};
- 所有
set_xxx必须加锁 + notify,所有get必须 wait + 检查异常 - 不要用
wait_until配合超时重试——那是为同步接口设计的,异步链里应靠回调驱动 - 如果用第三方库(如 boost.asio),优先走
post+handler路径,而不是包裹 std::future
链式异步最难的从来不是语法,而是确保每个节点的内存生命周期被至少一个智能指针持有着,且异常路径和正常路径都走到同一套完成通知机制里。漏掉任意一种,就会出现静默崩溃或卡死。


















