asyncio.gather默认短路失败,加return_exceptions=true可使异常以对象形式返回而非中断全部任务;需复用clientsession并显式检查结果类型。

直接用 asyncio.gather 并发发请求是可行的,但默认不带错误隔离——只要一个请求失败,整个 gather 就抛异常,其余任务也会被取消。这不是“批量并发”,而是“全有或全无”。
asyncio.gather 抛错时其他请求就中断了怎么办
这是最常踩的坑:gather 的默认行为是「短路失败」。比如你并发 10 个 HTTP 请求,第 3 个返回 404 或超时,剩下 7 个还在跑的任务会被强制 cancel,最终只拿到一个 Exception。
- 加
return_exceptions=True参数,让失败结果以Exception对象形式返回,而不是抛出 - 这样你能拿到混合列表:成功时是响应体(如
aiohttp.ClientResponse),失败时是TimeoutError或ClientConnectorError - 别在
gather外层套try/except——那只会捕获第一个错误,掩盖了其他任务的真实状态
和 asyncio.create_task + asyncio.wait 比有什么区别
gather 是高层封装,语义上表示「等所有协程完成并收集结果」;create_task + wait 更底层,适合需要控制完成时机(比如「等任意 1 个完成」或「等前 3 个完成」)的场景。
-
gather自动处理协程到 Task 的转换,也帮你做了结果顺序保序(按入参顺序) -
wait返回的是(done, pending)集合,不保序,也不自动 await 结果,得自己调task.result() - 如果你要限制并发数(比如同时最多 5 个请求),
gather本身不支持,得配合asyncio.Semaphore或用asyncio.as_completed+ 手动计数
实际发 HTTP 请求时要注意 aiohttp.ClientSession 复用
很多人把 ClientSession() 写在每个请求协程里,导致连接池失效、端口耗尽、DNS 重复解析。
- Session 必须跨请求复用,典型写法是作为参数传入协程,或用 async context manager 包裹整个
gather块 - 不要在循环里写
async with aiohttp.ClientSession() as session:—— 每次都新建 session - 如果用了
return_exceptions=True,记得对每个结果做类型判断:if isinstance(res, Exception): ...,否则调res.json()会报AttributeError
一个最小可运行示例(含错误处理和 session 复用)
import asyncio
import aiohttp
<p>async def fetch(session, url):
try:
async with session.get(url, timeout=2) as resp:
return await resp.text()
except Exception as e:
return e # 让 gather 收集异常,而非抛出</p><p>async def main():
urls = ['<a href="https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c">https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c</a>', '<a href="https://www.php.cn/link/8c4b0479f20772cb9b68cf5f161d1e6f">https://www.php.cn/link/8c4b0479f20772cb9b68cf5f161d1e6f</a>', '<a href="https://www.php.cn/link/c19fa3728a347ac2a373dbb5c44ba1c2">https://www.php.cn/link/c19fa3728a347ac2a373dbb5c44ba1c2</a>']
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(
*[fetch(session, url) for url in urls],
return_exceptions=True
)
for i, res in enumerate(results):
if isinstance(res, Exception):
print(f'URL {i} failed: {type(res).<strong>name</strong>}')
else:
print(f'URL {i} OK, len={len(res)}')</p><p>asyncio.run(main())</p>
真正容易被忽略的是:即使加了 return_exceptions=True,未处理的异常仍可能在 task 被销毁时触发 Task exception was never retrieved 警告——所以每个结果都必须显式检查类型,不能只靠 print 看一眼就完事。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











