co_await 不是自动暂停,它依赖 awaiter 的 await_ready、await_suspend、await_resume 三个方法;缺一不可,否则编译失败,且 await_suspend 的返回值直接决定协程挂起后的控制流走向。

co_await 不是“自动暂停”,它依赖 awaiter 的三个方法
直接写 co_await expr 不会无条件挂起协程——编译器会检查 expr 是否为“可等待对象”(awaiter),即是否提供 await_ready、await_suspend、await_resume 这三个成员函数。缺一不可,否则编译失败。
常见错误现象:error: no member named 'await_ready' in 'XXX',说明你传给 co_await 的类型没实现完整协议。
-
await_ready()返回bool:若返回true,跳过挂起,直接调用await_resume();返回false才进入挂起流程 -
await_suspend(std::coroutine_handle)是挂起的“控制开关”:返回void或true表示交出控制权;返回false会立即恢复(已挂起再恢复,开销略大);返回另一个std::coroutine_handle则切换执行目标 -
await_resume()必须有返回值,且类型需匹配co_await表达式期望的类型(比如int就不能返回void)
std::suspend_always 和 std::suspend_never 是最简 awaiter 实现
初学时别急着自己写 awaiter,先用标准库提供的两个“开关型”类型验证逻辑。它们不带状态、不调度、不延迟,纯粹用于控制挂起行为。
使用场景:调试协程帧生命周期、确认 promise_type 是否正常工作、或作为占位符快速验证协程结构是否能编译运行。
立即学习“C++免费学习笔记(深入)”;
-
co_await std::suspend_always{}:强制挂起,await_ready返回false,await_suspend默认不做任何事(控制权交还调用方) -
co_await std::suspend_never{}:绝不挂起,await_ready返回true,直接走await_resume(返回void) - 二者都不管理协程句柄生命周期,也不做资源调度——真异步必须自己实现
await_suspend中的调度逻辑(如投递到线程池、注册到 epoll)
co_await 后的恢复不是“自动唤醒”,需要外部触发
C++20 协程没有内置调度器,co_await 挂起后,协程句柄(std::coroutine_handle)就停在那里,内存还在,但 CPU 不再执行它。恢复必须由你显式调用 resume() 或 destroy()。
容易踩的坑:
- 把
std::coroutine_handle存在栈上,函数返回后句柄失效,再调resume()是未定义行为 - 忘记调
destroy()导致协程帧内存泄漏(尤其 promise_type 分配了堆内存时) - 在
await_suspend里保存句柄但没触发恢复,协程永远卡住(比如忘了调h.resume()或投递到事件循环)
典型做法:在 await_suspend 中把句柄交给异步操作(如 async_read 的 completion handler),等 I/O 完成后再调用 h.resume()。
自定义 awaiter 时,await_suspend 的返回值决定控制流走向
这是最容易被误解的一点:await_suspend 的返回类型不是可选的装饰,它直接决定协程挂起后的执行路径。
- 返回
void或bool(true):当前协程挂起,控制权回到调用方(比如 event loop 或主线程) - 返回
false:协程已挂起,但立刻恢复执行(相当于“挂起→唤醒”原子操作,可用于模拟同步等待) - 返回
std::coroutine_handle:当前协程挂起,同时恢复你返回的那个句柄——这实现了协程间的协作跳转(比如 A 等待 B,B 完成后直接切回 A) - 抛异常:当前协程不挂起,异常在
co_await表达式处抛出(注意不是在await_suspend内部捕获)
性能影响:返回 false 或句柄会绕过调度器,适合极低延迟场景;但若滥用,会丢失异步优势,变成伪协程。
复杂点在于,所有这些控制流都发生在编译器生成的状态机里,而你写的 await_suspend 只是其中一环——句柄是否有效、promise 是否存活、栈变量是否已被析构,全靠你自己保证。


















