为何aiohttp.ClientSession的.get()异步方法返回结果始终有序?
我曾疑惑,在多次调用服务器并等待所有回复返回时,aiohttp.ClientSession()是否会按顺序返回结果?我原本预期结果会是非有序的。此外,回复的速率存在差异,但奇怪的是结果仍保持有序。
我多次运行以下代码:
async def main(): async with aiohttp.ClientSession() as session: res_dict={} for number in range(1, 100): pokemon_url = f'https://pokeapi.co/api/v2/pokemon/{number}' async with session.get(pokemon_url) as resp: pokemon = await resp.json() res_dict[number]=pokemon['id'] print(pokemon['id']) for i in res_dict: print(i, '=',res_dict[i]) asyncio.run(main())
每次运行都得到完全有序的结果:
1 2 3 . . . 98 99
且结果与字典的键正确对应:
1=1 2=2 3=3 . . . 98=98 99=99
请问我的假设是否正确:aiohttp.ClientSession对象的.get()方法是否像asyncio.gather()一样,异步执行查询但仅按顺序返回结果?另外我注意到回复速率有差异,这是否是导致该现象的原因?
解答
你的代码并没有真正实现并发异步请求,这是结果始终有序的核心原因:
在你的
for循环中,每一次session.get()之后都立刻await了响应的json()解析,这意味着程序会等待当前请求完全处理完成后,才会发起下一个请求。整个过程是串行执行的,自然会按请求顺序返回结果,和回复速率差异无关——因为前一个请求没结束,后一个根本没发出去。asyncio.gather()的逻辑是同时发起所有任务,然后等待所有任务完成,最后按任务提交的顺序返回结果(即使某些任务先完成,也会等所有任务结束后再按原顺序输出)。但你的代码和这种并发模式完全不同,你是逐个发起并等待请求,本质是“同步执行异步函数”。
如果要验证非有序的情况,你需要先创建所有异步任务,再用asyncio.gather()并发执行,比如:
async def fetch_pokemon(session, number): pokemon_url = f'https://pokeapi.co/api/v2/pokemon/{number}' async with session.get(pokemon_url) as resp: pokemon = await resp.json() return number, pokemon['id'] async def main(): async with aiohttp.ClientSession() as session: tasks = [fetch_pokemon(session, num) for num in range(1, 100)] results = await asyncio.gather(*tasks) res_dict = dict(results) # 打印结果会看到顺序可能混乱,但字典键值对应正确 for num, pokemon_id in results: print(pokemon_id) for num in res_dict: print(num, '=', res_dict[num]) asyncio.run(main())
这时因为请求是并发发起的,回复速率的差异会导致打印的pokemon_id顺序可能混乱,但asyncio.gather()返回的结果列表仍然会和你提交任务的顺序一致,而字典的键值对应始终正确。
内容的提问来源于stack exchange,提问作者Anon Anon

