asyncio / aiohttp - Как сделать серию асинхронных - но зависимых - запросов? - PullRequest
3 голосов
/ 08 ноября 2019

У меня есть последовательность пар асинхронных запросов, в которых пара состоит из запроса A и запроса B. Кроме того, запрос B зависит от запроса A. Другими словами, мне нужно передать данные из ответа A в запрос B. Поэтому мне нужно запланировать задачи таким образом, чтобы каждая задача отправляла Запрос 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))

Теперь мой вопрос: для каждой задачи, состоящей из пары запросов,Гарантируется ли возвращение запроса до отправки запроса B? Пожалуйста, объясни. Я обеспокоен тем, что в рамках задачи, пока ожидается запрос A, может выполняться запрос B.

Если нет, как я могу сохранить задачи асинхронными и неблокирующими, но также убедиться, что в задаче запросБлокирует ли выполнение запроса B до тех пор, пока ответ A не вернется?

Я понимаю, что могу выполнить все вызовы запроса A в пакете, а затем выполнить все вызовы запроса B в пакете, но по причинам, характерным для моего варианта использования, Мне нужно запустить пакет из всех пар (запрос A, запрос B).

1 Ответ

4 голосов
/ 08 ноября 2019

Для каждой задачи, состоящей из пары запросов, гарантированно возвращается запрос A перед отправкой запроса B?

Да, преимущество шаблона асинхронности / ожидания состоит в том, что выне задавайте себе этот вопрос, последовательные строки кода всегда будут выполняться последовательно (, но не обязательно последовательно ). Здесь ваша функция request_chain гарантирует, что request_A всегда будет выполняться до request_B.

, пока ожидается запрос A, запрос B может выполнить

, чтоне произойдет, это в основном то, что означает wait: дождитесь, пока запрос A не вернется, прежде чем идти дальше . Другими словами, wait не влияет на порядок выполнения. Он просто передает элемент управления , поэтому время ожидания может использовать кто-то еще (в вашем случае любой код из другой (A, B) пары запросов).

Единственный код, который будет выполняться параллельно, - это тот, который вы запланируете самостоятельно (в этом случае, используя asyncio.gather, запланируйте несколько (A, B) пар для параллельного выполнения),

Я понимаю, что могу выполнить все вызовы Request A в пакете, а затем выполнить все вызовы Request B в пакете, но по причинам, характерным для моего варианта использования, мне нужно запустить пакет всех...

В этом конкретном случае, даже если вы можете запустить партию из A , то, по-моему, партию из B Ваше решение будет лучше, так как оно упрощает и подчеркивает связь между A и B .

Вот пример кода, который вы можете запустить, чтобы попробовать что-тоout (он делает то же самое, что вы делаете здесь с общедоступным математическим 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».

...