co_await只能在协程函数中使用,因其依赖promise_type提供挂起/恢复机制及协程帧管理;缺少promise_type定义会导致编译错误,且final_suspend必须返回suspend_always以避免内存错误。

co_await 为什么不能直接在普通函数里用
因为 co_await 只能在「协程函数」中使用,而协程函数必须有对应的 promise_type 定义,否则编译器无法生成挂起点、恢复逻辑和最终的协程帧管理代码。常见错误是写了 co_await 却没声明 promise_type,报错类似:error: use of undeclared identifier 'promise' 或 no type named 'promise_type' in 'std::coroutine_traits<...>'</...>。
解决办法:给返回类型(比如 Task)显式定义嵌套的 promise_type 结构,并实现至少三个关键接口:
-
get_return_object():返回协程句柄对应的任务对象(如Task{handle}) -
initial_suspend():控制协程启动时是否立刻挂起(通常返回suspend_always{}实现懒启动) -
final_suspend():控制协程结束时是否允许自动销毁帧(必须返回suspend_always{},否则可能访问已释放内存)
PromiseType 里 final_suspend 返回 suspend_never 会 crash
这是最易踩的坑之一。如果 final_suspend() 返回 suspend_never{},协程帧会在执行完 return 后立即被销毁,但此时外部可能还持有 coroutine_handle(比如调度器还没来得及回收),后续调用 handle.destroy() 就会触发 double-free 或访问野指针。
正确做法是强制挂起,把清理责任交给调度器:
立即学习“C++免费学习笔记(深入)”;
auto final_suspend() noexcept { return std::suspend_always{}; }
然后在调度器中轮询时检查 handle.done(),确认挂起后才调用 handle.destroy()。注意:不能依赖 handle.done() 在 final_suspend 前返回 true —— 它只在挂起后才稳定为 true。
如何让 Task 支持 co_await 其他 Task
要让一个 Task 能被 co_await,它必须提供 operator co_await() 成员函数,该函数返回一个 awaiter 对象;这个 awaiter 必须实现 await_ready()、await_suspend(coroutine_handle) 和 await_resume()。
关键点在于 await_suspend:不能直接 resume 目标 handle(容易导致栈溢出),而是应将其加入全局调度队列。示例节选:
struct Task {
struct promise_type {
Task get_return_object() { return Task{coroutine_handle<promise_type>::from_promise(*this)}; }
suspend_always initial_suspend() noexcept { return {}; }
suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
struct Awaiter {
coroutine_handle<promise_type> h;
bool await_ready() const noexcept { return !h || h.done(); }
void await_suspend(coroutine_handle<> caller) noexcept {
if (h && !h.done()) scheduler.enqueue(h); // 加入调度队列
}
void await_resume() const noexcept {}
};
auto operator co_await() && { return Awaiter{h}; }
private:
coroutine_handle<promise_type> h;
};
调度器 run() 循环里不检查 handle.done() 就 resume 会死循环
协程 handle 可能处于三种状态:未启动、运行中、已结束。直接 handle.resume() 一个已结束的 handle 是未定义行为(常见表现是程序卡死或 SIGSEGV)。必须先用 handle.done() 判断是否已终止。
典型调度循环写法:
void run() {
while (!queue.empty() || !idle_) {
auto h = queue.dequeue();
if (!h || h.done()) continue; // 关键防护
h.resume();
}
}
另外注意:resume 后 handle 状态可能立刻变为 done()(比如协程内直接 return),所以每次 resume 后都应重新检查,不能假定一次 resume 就“处理完毕”。
真正麻烦的是资源生命周期 —— promise_type 的析构时机由 final_suspend 和调度器 destroy 时机共同决定,漏掉任意一端,就会出现 promise 对象被提前析构,但 awaiter 还在尝试访问它的成员。


















