asyncio.cancellederror 继承自 baseexception,需显式捕获;task.cancel() 仅设标记,须 await 才真正退出;阻塞调用如 time.sleep 会阻碍取消,应改用 asyncio.sleep 或 run_in_executor。

asyncio.CancelledError 不是普通异常,不能用 Exception 捕获
很多人写 except Exception: 试图兜住协程取消,结果发现 asyncio.CancelledError 照样往上冒、任务直接崩。它继承自 BaseException,和 KeyboardInterrupt、SystemExit 同级,绕过所有 Exception 的拦截。
实操建议:
- 必须显式写
except asyncio.CancelledError:,别偷懒缩成except BaseException:(会吞掉真正不该忽略的崩溃) - 在
finally或async with里做清理比依赖异常捕获更可靠 - 如果用了第三方库(比如
aiohttp),注意它内部可能把CancelledError转成其他异常再抛出,得看文档确认行为
task.cancel() 后协程不会立刻停止,要 await task
调用 task.cancel() 只是设了个标记,协程还在跑,直到它自己检查到取消信号或遇到 await 点才真正退出。常见错误是 cancel 之后没 await task,就以为任务结束了,结果资源泄漏、状态错乱。
实操建议:
-
await asyncio.wait_for(task, timeout=1)比裸await task更安全,避免无限等待 - 若想强制超时后不管,用
asyncio.shield()包住关键清理逻辑,防止被 cancel 中断 - 在长循环里手动加
if task.cancelled(): break,尤其当协程里有 CPU 密集型计算时
asyncio.run() 内部会自动处理 CancelledError,但自建 event loop 会暴露细节
用 asyncio.run(main()) 时,顶层取消会被静默吞掉,你几乎感觉不到;但换成 loop.run_until_complete() 或 loop.run_forever(),未处理的 CancelledError 就会炸出来,还带一堆 traceback。
实操建议:
- 生产环境别手写 loop 启动逻辑,除非真需要定制信号处理或嵌入其他事件系统
- 如果必须手动管理 loop,记得在
run_until_complete外包一层 try/catchCancelledError -
asyncio.create_task()创建的任务默认不被asyncio.run()的取消传播覆盖,这点容易误判生命周期
协程里调用阻塞函数(如 time.sleep)会让取消失效
time.sleep(10) 这种同步阻塞会卡死整个 event loop,cancel 信号根本送不进去,task 状态永远是 pending,直到 sleep 结束才突然抛出 CancelledError——这已经晚了。
实操建议:
- 一律换用
await asyncio.sleep(10),它是可取消的 - 已有同步库(比如某些 SDK)没提供 async 接口?用
loop.run_in_executor()包一层,但要注意:executor 里的线程无法被 asyncio 取消,只能靠超时或外部中断 - 别在协程里用
os.system()、subprocess.call()这类阻塞调用,改用await asyncio.create_subprocess_exec()
协程取消不是开关,是协作式中断。最常被忽略的是:清理逻辑是否真的执行了,以及阻塞点是否真的可中断——这两处一漏,Cancel 就等于没发生。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











