asyncio.as_completed()异常:生成器仅返回首个元素的解决方法
解决异步生成器仅返回首个元素的问题
看起来你的代码里有几个关键问题导致了这个现象,咱们一步步排查并修复:
1. 重复创建ClientSession的隐患
你的fetch函数每次调用都会新建一个aiohttp.ClientSession,这不仅会浪费连接资源,还可能导致请求阻塞或意外中断。正确的做法是在整个程序生命周期内复用同一个Session。
2. 未处理协程异常
如果某个分页请求抛出异常(比如网络错误、API返回格式异常),await routine会直接抛出未捕获的异常,导致异步生成器提前终止,只返回之前已经yield的结果。必须给每个协程添加异常捕获逻辑。
3. 分页逻辑的小问题
你的paginate函数用offset <= size,但如果total是总条目数,当offset等于total时,请求的分页数据会是空的(没有可返回的条目),虽然这不会直接导致生成器停止,但会产生无意义的空结果,建议改成offset < total。
修正后的完整代码
import asyncio import aiohttp async def fetch(session, url, params=None): try: async with session.get(url, params=params) as response: response.raise_for_status() # 捕获4xx/5xx等HTTP错误状态码 return await response.json() except Exception as e: print(f"请求失败: {e}") return None # 或根据业务需求返回默认值 async def get_all_pages(session, base_url): def paginate(total_items): limit = 100 offset = 0 # 修正分页条件:避免请求空数据页 while offset < total_items: yield {"offset": offset, "limit": limit} offset += limit # 先获取总条目数,同时处理请求失败的情况 initial_response = await fetch(session, base_url) if not initial_response: return total = initial_response["data"]["total"] coroutines = [fetch(session, base_url, params) for params in paginate(total)] print(f"总条目数: {total}, 协程总数: {len(coroutines)}") # 遍历所有完成的协程,确保单个协程失败不影响整体 for future in asyncio.as_completed(coroutines): r = await future # 校验返回数据格式,避免格式异常中断生成器 if r and "data" in r and "field" in r["data"]: yield r["data"]["field"] async def main(): url = "http://arandomurl.com" # 全局复用一个ClientSession,提升请求效率 async with aiohttp.ClientSession() as session: results = [] async for x in get_all_pages(session, url): results.append(x) print(f"获取到的结果数量: {len(results)}") asyncio.run(main())
关键修改说明
- 复用Session: 在
main中创建唯一的ClientSession并传递给其他函数,避免重复创建Session带来的资源浪费和连接问题。 - 全链路异常处理: 在
fetch中捕获所有请求相关异常,同时校验返回数据格式,确保单个请求失败不会导致整个生成器终止。 - 优化分页逻辑: 修正分页条件,避免请求无数据的空分页。
这样修改后,异步生成器就能正确返回所有分页的结果,而不是仅返回第一个元素了。
内容的提问来源于stack exchange,提问作者Farhood ET
相关产品推荐
相关产品推荐

