← SEARCHABLE ARCHIVE
C / C++ · 2026-08-15

从 C++ 基础到掌握 — 第 21 集

理解 Coroutine 的挂起、恢复、生命周期与取消

INTERACTIVE READING MODE →

本博客文章以教学为目的编写。 内容可能存在错误,请结合目标编译器和标准库文档进行确认。

Coroutine 能挂起函数并在之后从相同状态继续。它不会自动创建线程或提供异步 I/O,而是构建异步控制流的语言级协议。

状态保存在 Coroutine frame 中

使用 co_await、co_yield 或 co_return 时,编译器会把函数转换为保存局部变量和恢复位置的状态机。

Task<Texture> LoadTextureAsync(Path path)
{
    FileBytes bytes = co_await ReadFileAsync(path);
    DecodedImage image = co_await DecodeAsync(std::move(bytes));
    co_return UploadTexture(std::move(image));
}

示例中的 Task 和 awaitable 必须由项目或异步库定义。标准 C++20 提供 Coroutine 转换规则,但没有提供通用 executor 或网络 event loop。

co_await 向 awaitable 询问如何挂起

await_ready   结果是否已可直接使用?
await_suspend 挂起后由谁持有 handle 并恢复?
await_resume  恢复后返回什么值或错误?

如果工作已完成,可以不挂起直接继续。否则必须安全地把 Coroutine handle 交给 scheduler 或 I/O,并且只恢复一次。

Frame 所有权是核心生命周期问题

调用者立即丢弃 Task 时,工作是取消、detach,还是由其他对象拥有?callback 恢复已销毁的 frame 会导致 use-after-free。

Coroutine 通过引用接收的对象也可能在挂起期间消失。

Task<void> ShowLater(const Player& player)
{
    co_await NextFrame();
    Draw(player.Name); // player 是否仍然存在?
}

可以把必要数据复制进 frame、使用稳定 handle,或明确要求调用者保证生命周期。

取消和错误也是协议。取消必须唤醒等待中的操作、运行清理并向上层 Task 传递结果。promise type 决定异常是在 co_await 处重新抛出,还是表示为错误值。

Coroutine 看起来像顺序代码,实际跨越多个时间点。必须文档化 frame 所有权、executor、恢复线程、取消和错误传播,并测试项目实际采用的 Task 实现。