asyncio.cancel() 不能强制终止协程,仅设取消标志;协程需主动检查cancelled()或在await点响应,否则CPU密集或阻塞操作会继续执行。

asyncio.cancel() 能否真正终止正在运行的协程
不能,asyncio.cancel() 只是设置取消标志,协程是否退出取决于它是否主动检查 cancelled() 或在 await 点响应取消。如果协程卡在纯 CPU 计算、阻塞 I/O(如 time.sleep())或没做任何 await,它会继续跑完。
- 协程必须在可等待对象(如
await asyncio.sleep()、await queue.get())上挂起,才能被中断 - 长时间同步运算(如循环累加百万次)不会响应取消,需手动插入
await asyncio.sleep(0)让出控制权 - 使用
loop.run_in_executor()执行的阻塞调用,取消只会标记 future,无法杀掉线程中的实际执行
如何在协程内部安全响应取消请求
核心是「尽早检查」和「及时清理」:每次从 await 返回后,先确认自己是否已被取消;资源获取后用 try/finally 或异步上下文管理器确保释放。
- 在关键 await 前后加
if task.cancelled(): raise asyncio.CancelledError(虽然通常不需显式写,但复杂逻辑中可增强可控性) - 打开文件、获取锁、连接数据库等操作后,务必用
async with或try/finally包裹,否则取消可能留下未关闭句柄 - 避免在
except Exception:中吞掉asyncio.CancelledError;它继承自BaseException,普通except:不会捕获它,但except BaseException:会——这很危险
async def fetch_with_timeout(url):
async with aiohttp.ClientSession() as session:
try:
async with session.get(url, timeout=5) as resp:
return await resp.text()
except asyncio.TimeoutError:
print("请求超时")
raise # 不要静默吞掉,让外层能处理取消或超时
cancel() 后为什么 await task 仍可能抛 CancelledError
因为 task.cancel() 不等于“任务已结束”,只是发起取消请求;真正完成要等你 await task 或调用 task.result() —— 此时若任务因取消而终止,就会抛出 asyncio.CancelledError。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 直接
await task是最常见触发点,别指望 cancel() 后就“万事大吉” - 若想忽略取消异常,需显式捕获:
try: await task except asyncio.CancelledError: pass - 用
asyncio.wait_for(task, timeout=None)包一层,不能防止 CancelledError,反而可能叠加超时异常,慎用 - 批量取消多个 task 时,建议用
asyncio.gather(*tasks, return_exceptions=True),避免一个失败中断全部
信号处理(如 Ctrl+C)下如何统一触发所有任务取消
主线程收到 SIGINT(Ctrl+C)时,默认会中断 run_forever(),但已调度的 task 不会自动取消——得自己监听并手动 cancel。
- 用
loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(shutdown()))注册清理逻辑 -
shutdown()函数里应遍历asyncio.all_tasks(),对非当前 task 调用task.cancel(),然后await asyncio.gather(*tasks, return_exceptions=True) - 注意:Python 3.9+ 的
asyncio.Runner提供了更简洁的run()+close()模式,但底层仍依赖同套取消逻辑 - 测试时别只按 Ctrl+C 看是否退出,要验证文件是否关闭、连接是否释放、日志是否写入完成
取消不是“杀死进程”,而是协作式中断。最容易被忽略的是:协程里那些看似无害的同步循环、没加 await 的重试逻辑、以及忘记在 finally 块里 close() 的异步资源——它们会让 cancel() 形同虚设。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










