Watch at Ease看見世界,讀懂生活。

科技 · 實用指南

await 已取消 task 時收到 CancelledError?不要吞掉 controller shutdown

child 完成 cancellation 時 controller 也可能被外部取消。先明確 ownership,再清理、收集 outcome 並驗證 outer cancellation 仍傳播。

Asyncio cancellation origin 圖
原創插畫:區分 child completion 與 controller cancel。 Watch at Ease

child 完成 cancellation 時 controller 也可能被外部取消。先明確 ownership,再清理、收集 outcome 並驗證 outer cancellation 仍傳播。

CancelledError 繼承 BaseException,cleanup 後通常 re-raise。 Task.cancel 只 request,不保證 coroutine 接受。

1. 列出 task owner 與 trigger

列出 controller、owned children、cancel 來源與 sibling fate,不要從 except-pass 開始。

await child 的 CancelledError 可能是 child 結束,也可能是 controller 外部取消;exception type 不能證明來源。

2. child cleanup 放入 finally

在 child try/finally 釋放資源並通常重新傳播 cancellation。cleanup 中的 await 也可能被取消。

async finalization 必須有小而明確的 boundary;shield 不可造成無限 shutdown。

3. 選 TaskGroup 或 gather

scope 擁有全部 children 且 sibling failure 應聯動時用 TaskGroup,離開 context 會等待。

獨立收集 child cancellation 可用 gather(return_exceptions=True);gather 本身被 cancel 仍會傳播。

4. 保留 controller cancellation

不要 blanket catch await child。手動時記錄 child state 與 current_task().cancelling,有 controller cancel 就 re-raise。

不要只為消除 state 呼叫 uncancel;這是特殊且刻意的 structured-concurrency decision。

5. 測試 simultaneous cancellation

測 child-only、controller-only、same-turn 三種情況並 assert cleanup、states 與 propagation。

exit 時只 audit owned tasks,確認無 pending、無 unretrieved exception,timeout 不會把 shutdown 變成功。

Asyncio shutdown primitive 圖
原創插畫:own tasks 並保留 outer cancellation。 Watch at Ease