不一定。协程取消时仅在 await 暂停点立即抛出 asyncio.cancellederror;纯 cpu 计算中取消信号会被暂存,直到下次 await 才触发。

协程取消时 asyncio.CancelledError 一定会抛出吗
不一定。只有在协程被取消时正处于 await 暂停点(如 await asyncio.sleep()、await queue.get()),才会立即触发 CancelledError;如果协程正在执行纯 CPU 计算(比如循环累加),取消信号会被暂存,直到下一次 await 才抛出。这意味着你不能假设 cancel() 调用后协程立刻退出。
实操建议:
- 测试时务必让待测协程在可取消点等待,例如用
await asyncio.sleep(1)或await asyncio.Event().wait() - 避免写
while True: pass这类无await的死循环,否则协程不会响应取消 - 若需在计算密集路径中响应取消,手动插入
await asyncio.sleep(0)让出控制权
如何可靠地触发并捕获取消信号
直接调用 task.cancel() 不等于立刻执行取消逻辑——它只是设置取消标记,实际抛异常依赖事件循环调度。必须配合 asyncio.wait_for() 或显式 await task 才能观察到行为。
示例(推荐写法):
import asyncio
<p>async def risky_work():
try:
await asyncio.sleep(2)
except asyncio.CancelledError:
print("got cancelled")
raise # 不建议吞掉,除非有清理逻辑</p><p>async def test_cancellation():
task = asyncio.create_task(risky_work())
await asyncio.sleep(0.1) # 确保 task 已启动并进入 sleep
task.cancel()
try:
await task # 必须 await 才会真正抛 CancelledError
except asyncio.CancelledError:
print("confirmed: task was cancelled")</p><p>asyncio.run(test_cancellation())
</p>
常见错误:只调用 task.cancel() 就结束函数,没 await task,导致取消未被感知。
asyncio.shield() 会让协程无法被取消吗
是的,但仅限被 shield() 包裹的那部分协程。它把内部协程“罩住”,外部取消请求不会穿透进去,直到它自己完成或主动抛出异常。
使用场景与风险:
- 适合保护关键清理逻辑,比如关闭连接、释放锁:
await asyncio.shield(close_db()) - 若误用于主业务协程(如
shield(some_api_call())),会导致整个任务无法被上层取消,阻塞调度 -
shield()不影响内部主动raise CancelledError,也不阻止loop.stop()强制终止
Python 3.12 中取消机制的兼容性变化
3.12 没有修改取消语义,但默认启用了 asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 在 Windows 上,这可能让某些旧测试代码因超时精度变化而偶然失败。
注意点:
- 不要依赖
task.cancelled()返回True后立刻抛异常——它只表示“已被请求取消”,不保证已执行 - 3.12 对
asyncio.timeout()的实现更严格,嵌套取消时优先级逻辑不变,但异常堆栈更清晰 - 若用
pytest-asyncio,确保版本 ≥ 0.24,否则asyncio.CancelledError可能被静默吞掉
最易忽略的是:取消不是中断,而是协作式通知——协程是否响应、何时响应,全看它有没有 await 和是否检查 task.cancelled()。别把它当成线程 kill()。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











