std::coroutine_handle不能直接链式调用then,因为c++20协程仅提供挂起/恢复机制,不内置任务调度与组合语义;需手动管理handle生命周期、状态流转及唤醒逻辑,并通过封装task类实现安全链式await。

为什么 std::coroutine_handle 不能直接链式调用 then
因为 C++20 协程本身不提供任务调度或组合语义,co_await 是挂起/恢复机制,不是“自动续跑”。你写 task.then([]{...}),底层没地方存这个回调,也没触发时机——除非你自己管调度、状态流转和唤醒逻辑。
常见错误现象:segmentation fault 或静默不执行,往往是因为 coroutine_handle 被销毁后还被尝试 .resume(),或者唤醒时目标协程已结束但没检查 .done()。
- 必须显式保存前一个协程的
coroutine_handle,并在它结束时手动唤醒下一个 - 每个
then回调需包装成新协程(用promise_type控制生命周期) - 不能依赖栈变量捕获:lambda 捕获的局部对象在父协程挂起后可能已析构
怎么让 then 返回可 co_await 的对象
核心是定义一个轻量级 task wrapper,比如 Task<t></t>,它内部持有 coroutine_handle<promise_type></promise_type>,且 promise_type 实现 get_return_object()、initial_suspend() 和 final_suspend()。重点在于 then 方法返回新的 Task<u></u>,并把当前 task 的完成事件绑定到新协程的启动上。
实操建议:
立即学习“C++免费学习笔记(深入)”;
-
then内部用co_spawn类似逻辑:新建协程,挂起等待前序完成,再执行回调 - 避免在
then中直接co_await原始 handle;要用封装后的Task,它能保证await_ready()判断是否已完成 - 回调函数必须是
noexcept或妥善处理异常,否则会调用std::terminate
示例关键片段:
一套面向小企业用户的企业网站程序!功能简单,操作简单。实现了小企业网站的很多实用的功能,如文章新闻模块、图片展示、产品列表以及小型的下载功能,还同时增加了邮件订阅等相应模块。公告,友情链接等这些通用功能本程序也同样都集成了!同时本程序引入了模块功能,只要在系统默认模板上创建模块,可以在任何一个语言环境(或任意风格)的适当位置进行使用!
template<typename T>
struct Task {
struct promise_type {
Task get_return_object() { return Task{coroutine_handle<promise_type>::from_promise(*this)}; }
suspend_never initial_suspend() { return {}; }
suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_value(T v) { value = std::move(v); }
std::optional<T> value;
};
// ...
};
co_await 一个 Task 时到底发生了什么
本质是调用了 Task::operator co_await(),它返回一个 awaiter 对象。该 awaiter 的 await_ready() 检查协程是否已结束(handle.done()),await_suspend() 把当前协程 handle 注册为前序 task 完成时的唤醒目标,await_resume() 返回结果值。
容易踩的坑:
- 忘记在
await_suspend()中保存当前 handle:导致唤醒丢失,协程永远挂起 -
await_ready()返回true但await_resume()还没准备好数据(比如value未赋值)→ UB - 多个
co_await同一个Task实例:第二次 await 时协程已结束,done()为 true,但若没缓存结果,可能读到未初始化值
性能和兼容性要注意哪些硬约束
协程帧分配方式直接影响能否安全链式调用:std::coroutine_handle 默认栈分配不可靠,必须用堆分配(如 operator new 在 promise_type::operator new 中重载),否则 then 创建的新协程可能因栈展开而失效。
编译器支持现状(截至 GCC 13 / Clang 16 / MSVC 19.35):
- MSVC 对
co_await表达式中临时对象生命周期处理最保守,建议显式命名中间Task变量 - Clang 在
-O2下可能优化掉未使用的coroutine_handle,导致await_suspend接收空 handle - 所有编译器都不支持跨线程直接 resume 同一个协程 handle,
then回调若需切线程,必须走外部调度器(如asio::post)
最易被忽略的一点:Task 移动构造函数必须正确转移 coroutine_handle 并置空原 handle,否则双重 resume 或 double-free。








