asyncio.gather默认一错即停,只抛出首个异常;设return_exceptions=True可捕获所有结果(含异常),需手动区分CancelledError与业务异常,并对失败项分类处理。

asyncio.gather 会静默吞掉异常?
不会吞掉,但默认行为是「一错即停」:只要任意一个协程抛出未捕获异常,asyncio.gather 就立刻中断所有正在运行的协程,并把第一个出错的异常原样抛出。你看到的往往只是第一个错误,后续协程的状态(是否已取消、是否残留资源)容易被忽略。
常见现象:asyncio.CancelledError 频繁出现,或日志里只记录了第一个失败任务,其余任务“消失”无踪。
- 用
return_exceptions=True参数可让gather把异常也当作返回值(类型为Exception实例),避免中断执行 - 此时返回结果是混合列表,需手动检查每个元素是否为异常:
isinstance(res, Exception) - 注意:即使启用了该参数,协程本身仍可能因超时、取消等原因中途退出,不保证全部执行完成
如何区分 CancelledError 和业务异常
asyncio.CancelledError 是取消信号,不是错误;它通常意味着父任务主动终止了子任务(比如超时、用户中止),而非代码逻辑缺陷。混在一起处理容易掩盖真实问题。
- 在
return_exceptions=True模式下,先过滤掉CancelledError:if isinstance(e, asyncio.CancelledError): continue - 对非取消类异常,建议单独记录(如用
logging.exception),并考虑是否要重试或降级 - 不要用
except Exception:一锅端——它会吃掉CancelledError,导致任务无法正常取消
并发请求中部分失败该怎么恢复
典型场景:调用 10 个 API,2 个 503,其余成功。你不想整个批次失败,但也不能假装没发生。
- 用
return_exceptions=True获取全部结果后,拆分为successes和failures两个列表 - 对失败项,检查
status属性(如果协程返回的是aiohttp.ClientResponse)或str(e)判断是否可重试(如网络超时 vs 401 认证失败) - 重试建议用
asyncio.sleep+ 循环,避免密集重试;不要在gather内部直接递归调用自身 - 注意:重试时别复用已取消的 session 或 connector,否则可能抛
RuntimeError: Session is closed
asyncio.gather 和 asyncio.create_task 哪个更可控
gather 简单,但控制粒度粗;真正需要细粒度错误隔离、独立生命周期管理时,create_task 更合适。
- 用
create_task启动每个协程后,用asyncio.wait(..., return_when=asyncio.FIRST_EXCEPTION)实现“出错即停”,或用asyncio.as_completed按完成顺序逐个取结果 - 每个 task 可单独
cancel()、检查task.done()/task.cancelled(),也能绑定不同异常处理器 - 代价是代码略冗长,且需自行管理 task 集合(避免被 GC 提前回收)
实际中最容易被忽略的点:协程内部没做异常兜底,又没开 return_exceptions=True,结果整个并发流因一个 KeyError 全挂,还查不出哪条数据触发的。上线前务必在测试里模拟单个协程 raise 异常,看主流程是否按预期降级。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











