Python asyncio 超时与取消处理的四个实战要点
前言
在 Python 异步服务中,一个没有超时保护的 await 就可能把整个事件循环里的并发任务拖住:下游接口卡死,协程永远挂起,连接和内存持续堆积。本文结合实际踩坑,整理 asyncio 中超时与取消处理的四个要点。
1. 给每个外部调用加上超时
最直接的做法是 asyncio.wait_for。超时后它会取消内部任务并抛出 TimeoutError。Python 3.11 之后更推荐 asyncio.timeout 上下文管理器,可以一次覆盖多个 await,语义也更清晰。
import asyncio
async def fetch(url):
await asyncio.sleep(5) # 模拟慢接口
return url
async def main():
try:
async with asyncio.timeout(2):
await fetch("https://example.com")
except TimeoutError:
print("请求超时,走降级逻辑")
asyncio.run(main())
2. 取消是通过异常传递的
任务被取消时,await 处会抛出 CancelledError。它在 3.8 之后继承自 BaseException,所以 except Exception 抓不到。清理资源请放进 finally;如果确实捕获了 CancelledError,处理完必须重新 raise,否则上层的超时和取消都会失效。
3. 并发任务优先使用 TaskGroup
asyncio.gather 在某个子任务失败后,其余任务仍会继续运行,容易留下孤儿任务。TaskGroup 会在任一子任务异常时自动取消其他任务,并等待它们全部退出,生命周期更容易管理。
4. 阻塞调用要放到线程里
time.sleep、同步的 requests 调用会卡住整个事件循环,此时超时也无法触发,因为事件循环根本没机会调度。用 asyncio.to_thread 把阻塞函数交给线程池,再配合 asyncio.timeout 使用即可。需要注意:线程里的代码无法被真正取消,只是不再等待它的结果。
小结
排查清单:外部调用是否都有超时;except 中是否吞掉了 CancelledError;并发任务是否有统一的取消边界;是否混入了阻塞调用。按这四点逐项检查,大多数异步服务的“卡死”问题都能定位到。
- 点赞
- 收藏
- 关注作者
评论(0)