← 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 實作。