std::stop_token不能直接co_await因其缺少await_ready等可等待接口;需通过stop_callback绑定协程handle实现挂起恢复,配合自定义stop_awaiter并确保stop_source生命周期覆盖协程全程。

std::stop_token 在协程中为何不能直接用作 co_await 等待对象
因为 std::stop_token 本身不是可等待类型(即没有 await_ready、await_suspend、await_resume 成员),直接写 co_await stop_token 会编译失败。它只提供轮询接口(stop_requested())和注册回调(register_callback()),而协程需要的是异步挂起能力。
如何让协程响应停止请求:用 std::stop_callback + promise_type 自定义挂起
核心思路是:在协程 promise 中持有 std::stop_callback,当停止被请求时自动恢复协程;同时配合自定义的可等待类型(如 stop_awaiter)实现「挂起直到停止」语义。常见错误是只轮询 stop_token.stop_requested() 却不挂起,导致忙等。
- 必须在
promise_type的get_return_object()或构造函数中绑定std::stop_callback,捕获协程 handle 并调用resume() -
stop_awaiter的await_ready()应返回token.stop_requested(),避免无谓挂起 - 注意
std::stop_callback构造时若 token 已停止,回调会立即执行 —— 此时协程可能尚未挂起,需在await_suspend中检查是否已就绪 - 别把
std::stop_source和std::stop_token生命周期搞混:token 必须比 callback 长,否则回调触发时访问 dangling token
一个最小可行的 stop_awaiter 实现(C++20)
struct stop_awaiter {
std::stop_token token;
bool await_ready() const noexcept { return token.stop_requested(); }
void await_suspend(std::coroutine_handle h) noexcept {
// 停止源可能已在 await_suspend 前触发,所以先检查
if (token.stop_requested()) return;
// 绑定回调:触发时 resume 当前协程
cb.emplace(token, [h] { h.resume(); });
}
void await_resume() noexcept {}
private:
std::optional<:stop_callback>> cb;
};
</:stop_callback>
使用时:co_await stop_awaiter{stop_token};。注意 std::stop_callback 是 move-only,必须用 std::optional 延迟构造,否则 token 若无效会导致构造失败。
协程外如何安全触发终止:stop_source 的生命周期与传递时机
最容易出错的是在协程已结束或 promise 已销毁后调用 stop_source.request_stop() —— 这不会 crash,但回调不会执行(因为绑定的 handle 已失效)。关键点:
-
std::stop_source应随协程对象(如task<t></t>)生命周期存在,不能是局部变量 - 向协程传递
std::stop_token时,确保它来自同一个std::stop_source,且该 source 至少活到协程结束 - 若协程有多个子任务,每个都应接收独立 token(来自同一 source),而非共享一个 token 后自行管理回调
- 不要在
await_resume()里调用request_stop()—— 此时协程已恢复,再停没意义;真正退出逻辑应在co_await stop_awaiter{...}之后处理
协程协同终止不是“中断”,而是协作式信号;真正复杂的是跨多层嵌套协程时的 token 传递和 source 生命周期对齐,这点容易被忽略。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











