asyncio.Task.cancel() 立即标记任务为取消但不中断执行,而是在下一个 await 点抛出 CancelledError,协程需在 try/finally 或 async with 中主动处理清理;task.done() 为 False 是正常现象,因清理可能耗时但不阻塞事件循环。

cancel() 后为什么立刻抛出 CancelledError?
因为 asyncio.Task.cancel() 不是“温柔喊停”,而是给协程打上“已取消”标记,并在下一个 await 点主动触发 CancelledError —— 这是 asyncio 的协作式取消机制,不是强制中断。协程必须自己响应这个异常,否则清理逻辑根本没机会跑。
常见错误现象:task.cancel() 调用了,但 finally 没执行、async with 没退出、socket 没 close、文件没 flush。
- 协程里没写
try/except CancelledError或没用finally - 在
await之前就卡死(比如死循环、CPU 密集型计算),永远等不到抛异常的时机 - 用了
loop.run_in_executor调用阻塞函数,取消对线程内执行无影响
如何安全退出并确保资源清理?
核心原则:把清理逻辑放在 finally 块里,或用 async with / async for 自动管理。Cancel 之后,协程会在下一个可挂起点被中断,然后流程自然进入 finally。
使用场景:HTTP 客户端连接、数据库连接池、临时文件写入、长轮询任务。
立即学习“Python免费学习笔记(深入)”;
示例:
async def fetch_data():
conn = None
try:
conn = await aiohttp.ClientSession()
resp = await conn.get("https://httpbin.org/delay/5")
return await resp.text()
finally:
if conn is not None:
await conn.close() # 这行一定会执行- 不要在
try中直接return,绕过finally - 避免在
finally里做新的await(除非你确认它不会被取消;否则可能再抛一次CancelledError) - 若清理本身可能耗时,考虑加超时:
asyncio.wait_for(cleanup(), timeout=2)
cancel() 调用后,task.done() 为 False 是正常吗?
是正常的。调用 task.cancel() 只是设置取消状态,真正完成要等协程响应并退出 —— 中间可能还有 await、finally、甚至嵌套的子任务等待。
性能 / 兼容性影响:如果协程在 finally 里做了重试、日志上传等耗时操作,task.done() 会延迟返回 True,但不会卡住事件循环(前提是没写死循环或阻塞调用)。
- 别用
while not task.done(): await asyncio.sleep(0.01)等待 —— 改用await task或asyncio.wait([task], return_when=asyncio.FIRST_COMPLETED) -
task.cancelled()返回True表示已被请求取消,不等于已完成 - 若想等清理结束再继续,就
await task;若不想阻塞,监听task的完成回调(add_done_callback)
CancelledError 被吞掉怎么办?
最常发生在你 catch 了 Exception 却没单独处理 CancelledError,或者用了第三方库的异常屏蔽逻辑(比如某些 retry 装饰器默认忽略所有异常)。
错误示例:
try:
await do_something()
except Exception: # ← 这里吞掉了 CancelledError
log.error("failed")- 显式捕获
asyncio.CancelledError并重新抛出(除非你真要压制它) - 用
except BaseException:要格外小心 —— 它包含CancelledError和KeyboardInterrupt - 检查所用异步库(如
aiofiles,aiomysql)是否支持取消;老版本可能忽略取消信号
复杂点在于:取消可能发生在任意 await 点,而清理逻辑又依赖当前协程上下文是否还有效。最容易被忽略的是——你以为 finally 万无一失,但若协程在 await 前崩溃(比如 raise 了别的异常),或取消发生在 __aexit__ 执行中途,资源仍可能泄漏。










