协程挂起时不能直接return,因为会销毁协程帧导致恢复时未定义行为;正确做法是await_suspend返回coroutine_handle或true/false,由调度器显式resume。

协程挂起时为什么不能直接 return?
因为 co_await 表达式挂起后,控制权交还给调用方(比如调度器),此时协程帧仍需保留在堆上供后续恢复;若在 await_transform 或 await_suspend 中提前 return,会导致协程状态机被销毁,再恢复就会触发未定义行为——常见表现是程序崩溃或静默失败。
正确做法是让 await_suspend 返回 std::coroutine_handle,由调度器显式调用 resume();或者返回 false 表示不挂起(立即继续执行)。
-
await_suspend返回true:必须确保 handle 不会被销毁,且后续有且仅有一次resume() - 返回
std::coroutine_handle:等价于返回true+ 自动调用该 handle 的resume(),但更安全(避免裸 handle 误用) - 返回
void或false:协程不挂起,继续同步执行
如何让 task 对象支持 co_await 并入队?
关键在于为自定义 task 类提供 operator co_await(),它返回一个 awaiter 类型;该 awaiter 必须实现 await_ready()、await_suspend() 和 await_resume()。其中 await_suspend 负责把当前协程 handle 插入任务队列。
典型结构:
struct task {
struct promise_type {
task get_return_object() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
};
// operator co_await 返回自定义 awaiter
auto operator co_await() && {
struct awaiter {
task& t;
bool await_ready() const noexcept { return false; }
void await_resume() const noexcept {}
std::coroutine_handle await_suspend(
std::coroutine_handle h) noexcept {
// 把 h 入队到全局/线程局部调度器
scheduler::enqueue(h);
return std::noop_coroutine(); // 不立即恢复
}
};
return awaiter{*this};
}
};
注意:await_suspend 中不要捕获协程局部变量(如 lambda 捕获 this),因为协程帧可能已被移动;只应操作传入的 std::coroutine_handle。
调度器 enqueue 后何时 resume?
调度器本身不主动“驱动”协程,它只是个容器 + 触发点。真正恢复协程的时机取决于你设计的执行模型:
- 如果用单线程轮询(如
scheduler::run_until_idle()),则在每次循环末尾遍历队列并调用handle.resume() - 如果对接系统事件(如 I/O 完成、定时器到期),则在对应回调中调用
handle.resume() - 切忌在
await_suspend内直接h.resume()—— 这会绕过队列,变成同步调用,失去“任务排队”语义
另外,resume() 只能调用一次;重复调用导致未定义行为。可考虑在 await_resume() 中置空 handle 或加原子标记防止重入。
为什么 final_suspend 不能用 suspend_never?
因为 final_suspend 控制协程结束时是否自动销毁帧。若返回 std::suspend_never,协程会在执行完 promise_type::unhandled_exception() 或正常返回后立即析构 promise,导致 task 对象持有的协程 handle 指向已释放内存。
必须返回 std::suspend_always 或自定义 awaiter,并在 await_suspend 中手动管理生命周期(例如将 handle 转交给调度器或 future 对象)。否则,哪怕任务已执行完毕,只要外部还持有 task,就可能因访问已销毁帧而出错。
常见错误现象:double free、use-after-free、segmentation fault 在 task 析构或第二次 co_await 时爆发。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











