asyncio / aiohttp-如何进行一系列异步但依赖的请求?

时间:2019-11-07 22:59:17

标签: python asynchronous async-await python-asyncio aiohttp

我有一系列异步请求,其中一对异步请求包含请求A和请求B。此外,请求B依赖于请求A。换句话说,我需要将数据从响应A传递到请求B。因此,我需要安排任务,以便每个任务都发送请求A,然后仅在响应A返回之后才发送请求B。

from aiohttp import ClientSession
from typing import *
import asyncio

async def request_A(url: str, session: ClientSession) -> dict:
    async with session.request('get', url) as response:
        return await response.json()

async def request_B(url: str, data: dict, session: ClientSession) -> dict:
    async with session.request('post', url, json=data) as response:
        return await response.json()

async def request_chain(url_A: str, url_B: str, session: ClientSession) -> dict:
    response_A_data = await request_A(url_A, session)
    response_B_data = await request_B(url_B, response_A_data, session)
    return response_B_data

async def schedule(url_chains: List[Tuple[str, str]]) -> list:
    tasks = []
    async with ClientSession() as session:
        for url_chain in url_chains:
            url_A, url_B = url_chain
            task = asyncio.create_task(request_chain(url_A, url_B, session))
            tasks.append(task)
        return await asyncio.gather(*tasks)

def run_tasks(url_chains: List[Tuple[str, str]]) -> list:
    return asyncio.run(schedule(url_chains))

现在,我的问题是:每个由一对请求组成的任务是否都能保证请求A在发送请求B之前返回?请解释。我担心在任务中,在等待请求A的同时,请求B可能会执行。

如果没有,如何使任务保持异步和非阻塞状态,又如何确保任务A中的请求A阻塞请求B的执行,直到响应A返回?

我了解我可以成批运行所有请求A调用,然后成批运行所有请求B调用,但是由于特定于我的用例,我需要运行全部(请求A,请求B )对。

1 个答案:

答案 0 :(得分:4)

  

每个任务由一对请求组成,即请求A   保证在发送请求B之前会返回?

是的,异步/等待模式的优点是您不必问自己这个问题,连续的代码行将始终按顺序执行(但不一定连续执行)。在这里,您的函数request_chain确保request_A将始终在request_B之前执行。

  

正在等待请求A,请求B可能会执行

那不会发生,基本上就是await的意思:请继续,直到请求A返回后再继续操作。换句话说,await对执行顺序没有影响。它只是提供控制,因此其他人可以使用中间时间(在您的情况下,来自另一个(A,B)请求的任何代码对)。

唯一可以并行执行的代码是您自己安排的代码(在这种情况下,使用asyncio.gather,安排要并行执行的几对(A,B) ) 。

  

我知道我可以批量运行所有请求A调用,然后批量运行所有请求B调用,但是由于特定于我的用例,我需要运行所有所有...的批处理...

在这种情况下,即使您可以运行一批 A 然后一批 B ,我认为您的解决方案也会更好以更简单的方式突出显示 A B 之间的关系。

这是一个示例代码,您可以运行该代码来进行尝试(与使用公共数学API进行的操作相同),只需两步即可计算出“ x * 2 + 2”,第一步是“ * 2”(等同于请求 A ),然后是“ +2”(等同于请求 B ):

MATH_API_URL = "http://api.mathjs.org/v4"

from aiohttp import ClientSession
import asyncio

async def maths(session, url, expression):
    params = {"expr" : expression}
    print(f"\t> computing {expression}")
    async with session.get(url, params=params) as response:
        result = await response.text()
        print(f"\t< {expression} = {result}")
        return result

async def twice(session, x):
    return await maths(session, MATH_API_URL, f"2 * {x}")

async def plus_two(session, x):
    return await maths(session, MATH_API_URL, f"2 + {x}")

async def twice_plus_two(session, x):
    twice_x = await twice(session, x)
    return await plus_two(session, twice_x)

async def main(inputs):
    async with ClientSession() as session:
        return await asyncio.gather(*(twice_plus_two(session, x) for x in inputs))

inputs = list(range(3))
print([x*2+2 for x in inputs])
print(asyncio.run(main(inputs)))

此代码输出安排请求的顺序:

[2, 4, 6]    
    > computing 2 * 0    
    > computing 2 * 1    
    > computing 2 * 2    
    < 2 * 1 = 2    
    > computing 2 + 2
    < 2 * 0 = 0    
    > computing 2 + 0
    < 2 * 2 = 4    
    > computing 2 + 4
    < 2 + 2 = 4    
    < 2 + 4 = 6
    < 2 + 0 = 2
['2', '4', '6']

查看“ +2”返回后如何安排“ +2”。