簡體   English   中英

asyncio/aiohttp - 如何進行一系列異步 - 但依賴 - 請求?

[英]asyncio/aiohttp - How to make series of async - but dependent - requests?

我有一系列異步請求對,其中一對由請求 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 阻止請求 B 的執行,直到響應 A 返回?

我知道我可以批量運行所有請求 A 調用,然后批量運行所有請求 B 調用,但由於特定於我的用例的原因,我需要運行所有(請求 A,請求 B)對的批處理。

對於由一對請求組成的每個任務,請求 A 是否保證在請求 B 發送之前返回?

是的,async/await 模式的優點是您不必問自己這個問題,連續的代碼行將始終按順序執行(但不一定是連續執行)。 在這里,您的 function request_chain保證request_A將始終在request_B之前執行。

在等待請求 A 時,請求 B 可以執行

這不會發生,這基本上就是await的意思:堅持到請求 A 返回,然后再繼續 換句話說, await對執行順序沒有影響。 它只是交出控制權,因此其他人可以使用隱藏時間(在您的情況下,來自另一個(A, B)請求對的任何代碼)。 這就是為什么連續的代碼行不一定連續執行的原因,使用await將控制權交給其他一些 協程(我們剛剛提到的其他人)允許這個協程在 A 和 B 之間執行代碼。

即使這有點不准確,您也可以記住:唯一將並行執行的代碼是您自己安排的代碼(在這種情況下使用asyncio.gather ,安排幾個(A, B) 對並行執行) .

我知道我可以批量運行所有請求 A 調用,然后批量運行所有請求 B 調用,但是由於特定於我的用例的原因,我需要運行一批所有...

在這種特殊情況下,即使您可以先運行一批A ,然后再運行一批B ,我認為您的解決方案會更好,因為它以更簡單的方式突出了AB之間的關系。

這是您可以運行來嘗試的代碼示例(它與您在此處使用公共數學 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”。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM