asyncio.cancel() 不立即终止任务,仅设取消标志;任务需在 await 点捕获 CancelledError 才退出,否则因阻塞调用或未处理异常而“假取消”,须配合 await asyncio.wait_for(task, timeout) 确保真正结束。

asyncio.cancel() 为什么有时不生效
直接调用 task.cancel() 只是设置取消标志,任务不会立即终止——它得自己在下一个 await 点检查 CancelledError 并退出。如果任务卡在纯 CPU 计算、阻塞 I/O(比如 time.sleep() 或未用 asyncio.to_thread() 包装的同步文件读写),或者忘了加 try/except CancelledError,就会“假取消”。
常见现象:调用 cancel() 后 task.done() 仍是 False,task.cancelled() 是 True,但协程还在跑。
- 必须确保协程内部能响应取消:所有
await调用都应来自支持取消的 asyncio 原语(如asyncio.sleep()、asyncio.wait_for()、asyncio.create_task()等) - 避免在协程中使用
time.sleep()、requests.get()等阻塞调用;改用await asyncio.sleep()或await asyncio.to_thread(requests.get, ...) - 若需在长时间计算中主动检查取消状态,可定期插入
if asyncio.current_task().cancelled(): raise asyncio.CancelledError
如何等待取消真正完成(避免 task.cancelled() 为 True 却没退出)
task.cancel() 不等于“已结束”。要确认任务已退出,得显式等待它完成,否则可能引发资源泄漏或竞态问题(比如任务还在写文件,主逻辑已认为它结束了)。
- 推荐组合:先
task.cancel(),再await asyncio.wait_for(task, timeout=5.0)—— 这样既设取消标志,又强制最多等 5 秒 - 不要只写
await task,因为如果任务忽略取消,会无限挂起 - 捕获
asyncio.CancelledError是可选的,但捕获后**必须重新抛出**(或不做处理),否则取消会被吞掉:try: await task except asyncio.CancelledError: raise # 必须重抛,否则取消失效 except Exception: # 处理其他异常 pass
取消带子任务的父任务(例如 gather 中的任务)
用 asyncio.gather() 启动多个任务时,对 gather 返回的 task 调用 cancel(),默认**不会递归取消子任务**——子任务继续运行,变成孤儿任务。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
- 加参数
return_exceptions=False(默认)+asyncio.shield()不能解决这个问题;正确做法是手动管理子任务引用 - 安全做法:保存每个子任务对象,取消时逐个调用
.cancel(),再统一await asyncio.gather(*tasks, return_exceptions=True) - 更简洁方案:用
asyncio.create_task()显式创建子任务,并在父作用域持有引用;取消逻辑由上层控制,而非依赖 gather 的生命周期 - 注意:
asyncio.wait_for(task, ...)对 gather 任务有效,但它只等 gather 本身完成,不保证子任务已退出——仍需额外跟踪子任务状态
取消正在执行的 asyncio.to_thread() 或 subprocess
这些场景下取消最难收尾:线程或子进程无法被 Python 协程直接中断,只能靠协作式退出或系统级信号。
-
asyncio.to_thread()中的函数若不响应threading.Event或超时参数,取消只会让外层协程返回,但线程仍在后台跑——需函数自身支持中断(如传入stop_event参数并轮询) - 对于
asyncio.create_subprocess_exec(),调用proc.terminate()或proc.kill()需在CancelledError处理块中显式触发:try: await proc.wait() except asyncio.CancelledError: proc.terminate() try: await asyncio.wait_for(proc.wait(), timeout=3.0) except asyncio.TimeoutError: proc.kill() await proc.wait() raise - 永远不要假设
proc.kill()立即生效;子进程可能正在处理信号、写磁盘或死锁,需配合超时和重试
真正安全的取消不是调一个方法,而是从协程设计开始就考虑可中断性:避免阻塞、暴露退出钩子、分段 await、主动检查取消状态。最易被忽略的是——取消后不等待完成,就以为万事大吉。

















