c++oding="utf-8" ?>
c++标准库不支持原生链式异步任务,std::future仅单次消费且无.then();必须用std::promise手动管理状态、异常传播与执行上下文,自行实现完备的状态机和资源生命周期控制。

如何用 std::future 和 std::promise 构建可链式调用的异步任务
直接说结论:C++ 标准库本身不提供“任务链式顺序执行”的原生抽象,std::future 是单次消费、不可重用的,且不支持 .then() 这类延续操作;强行基于它做链式调度,必须自己封装状态流转和线程调度逻辑,否则会遇到阻塞、重复获取、移动后失效等硬伤。
典型错误现象是:连续写两个 get() 调用,第二个直接抛出 std::future_error: No state;或者把 std::future 存在容器里反复取值,结果第一次取完就崩溃。
- 每个
std::future只能调用一次get()或wait()(后者不取值但会消耗状态) - 移动构造后的
std::future会置空原对象,原对象再调用get()就报错 -
std::async默认策略是std::launch::async | std::launch::deferred,可能延迟执行,导致链式等待逻辑错乱 - 若想链式触发后续任务,必须在前一个任务完成回调中显式启动下一个——这需要手动管理
std::promise的 fulfill 时机
为什么不要直接返回 std::future 做链式接口
常见误区是设计类似 task.then([](int x) { return x * 2; }) 的接口,然后内部试图用 std::future::then() ——但 C++20 之前根本不存在这个成员函数,C++20 引入的是 std::experimental::future,非标准且各大编译器支持不一(GCC 12+ 未默认启用,MSVC 需 /std:c++20 + /experimental:module)。
真正可行的路径是:用 std::promise 手动控制完成信号,并把后续任务包装成 lambda,在前序 std::future 的获取点(或线程内)触发。这意味着你得自己解决:
- 异常传播:前一个任务抛异常,后续 promise 必须
set_exception(),否则挂起 - 类型擦除:不同阶段返回类型不同(
int→std::string→double),不能只用一个模板参数固定 - 执行上下文:谁来跑后续 lambda?主线程轮询?新线程?线程池?C++ 标准库不负责这个
一个轻量但可用的链式任务基类实现要点
下面是一个最小可行结构,不依赖第三方库,仅用 C++17:
template<typename t>
class Task {
std::shared_ptr<:promise>> m_promise;
std::function<void> m_work;
<p>public:
explicit Task(std::function<void> work)
: m_promise(std::make_shared<:promise>>()), m_work(std::move(work)) {}</:promise></void></p>
<pre class="brush:php;toolbar:false;">template<typename U>
Task<U> then(std::function<U(T)> f) {
auto next_promise = std::make_shared<std::promise<U>>();
auto self = std::make_shared<Task<T>>(*this); // 捕获当前 task 状态
auto wrapped = [self, f, next_promise]() mutable {
try {
T val = self->get(); // 这里会阻塞,仅作示意;实际应走回调机制
next_promise->set_value(f(val));
} catch (...) {
next_promise->set_exception(std::current_exception());
}
};
// 真实项目中这里应投递到线程池或 executor,而非直接调用
std::thread(wrapped).detach();
return Task<U>{[next_promise]() { /* 占位,真实由外部驱动 */ }};
}
T get() {
return m_promise->get_future().get();
}
void start() {
std::thread([this]() {
try {
m_work();
// 假设 work 内部会调用 m_promise->set_value(...)
} catch (...) {
m_promise->set_exception(std::current_exception());
}
}).detach();
}
};
注意:then() 里直接 self->get() 是阻塞式写法,仅用于演示逻辑流;生产环境必须改成非阻塞回调注册(例如用 std::future::wait_for 轮询 + 条件变量,或借助 std::jthread + std::stop_token 实现可取消等待)。
最容易被忽略的三个细节
很多实现跑通简单 case 就以为 OK,但在并发压测或异常路径下立刻崩:
-
std::promise的生命周期必须长于所有对它get_future()的使用——如果Task对象析构了但还有线程在等它的future,就会std::terminate - 多个
then()并发调用同一前置Task,若没加锁或原子状态判断,可能多次set_value()导致未定义行为(std::promise不允许多次设置) - 没有处理
std::future_status::deferred状态:当std::async返回 deferred future 时,wait()不起作用,必须显式调用get()才触发执行
真正健壮的链式任务模型,核心不在语法糖多漂亮,而在 promise 状态机是否完备、异常是否透传、资源是否自动回收——这些都得自己一行行写清楚,标准库只给砖,不给楼。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











