asyncio aiohttp:与睡眠重叠IO

当所有协同程序都在等待时,asyncio会侦听事件以再次唤醒它们 . 一个常见的例子是 asyncio.sleep() ,它记录一个定时事件 . 实际上,事件通常是准备接收或发送新数据的IO套接字 .

为了更好地理解这种行为,我设置了一个简单的测试:它向localhost发送一个http请求并等待响应 . 在localhost上,我已经设置了一个烧瓶服务器,在响应之前等待1秒钟 . 发送请求后,客户端休眠1秒钟,然后等待响应 . 我希望这会在一秒钟内返回,因为我的程序和服务器应该并行睡眠 . 但它需要2秒钟:

import aiohttp
import asyncio
from time import perf_counter

async def main():
    async with aiohttp.ClientSession() as session:

        # this http request will take 1 second to respond
        async with session.get("http://127.0.0.1:5000/") as response:

            # yield control for 1 second
            await asyncio.sleep(1)

            # wait for the http request to return
            text = await response.text()
            return text

loop = asyncio.get_event_loop()

start = perf_counter()
results = loop.run_until_complete(main())
stop = perf_counter()

print(f"took {stop-start} seconds") # 2.01909

What is asyncio doing here, why can't I overlap waiting times ?

我对HTTP请求的具体场景不感兴趣,aiohttp仅用于构造示例 . 这可能有点危险:这可能与aiohttp有关,而不是asyncio .

实际上,我预计会出现这种情况(因此关于asyncio和aiohttp的问题 Headers ) . 我的第一个直觉是在调用_1027669之前可能没有发送请求所以我重新排序了一下:

# start coroutine
text = response.text()

# yield control for 1 second
await asyncio.sleep(1)

# wait for the http request to return
text = await text

但这还需要两秒钟 .

好的,现在要确保请求在进入睡眠状态之前被发送,我将 print("incoming") 添加到服务器上的路由,然后再进入休眠状态 . 我还在客户端将睡眠时间长度更改为10秒 . 服务器在客户端运行后立即打印传入 . 客户端总共需要11秒 .

@app.route('/')
def index():
    print("incoming")
    time.sleep(1)
    return 'done'

由于HTTP请求是立即生成的,服务器肯定在客户端从_1027673唤醒之前发出了答案 . 在我看来,提供HTTP请求的套接字应该在客户端唤醒后立即就绪 . 但是,总运行时间总是增加客户端和服务器等待时间 .

我是不是在某种程度上滥用asyncio,或者这毕竟与aiohttp有关?

回答(2)

3 years ago

问题是服务器中发生的一秒钟在 async with session.get("http://127.0.0.1:5000/") as response: 中执行 .

http请求在您获得此 response 对象之前完成 .

您可以通过以下方式测试:

...
async def main():
    async with aiohttp.ClientSession() as session:

        start = perf_counter()
        # this http request will take 1 second to respond
        async with session.get("http://127.0.0.1:5000/") as response:
            end = perf_counter()
            print(f"took {end-start} seconds to get response")
            # yield control for 1 second
            await asyncio.sleep(1)

            # wait for the http request to return
            text = await response.text()
            return text
...

顺便说一下,只要你有另一个正在运行的协程,你肯定可以重叠这个等待时间 .

3 years ago

您的测试代码有三个等待(两个显式和一个隐藏在 async with 中),因此您不会得到任何并行等待 . 测试您描述的场景的代码类似于:

async def download():
    async with aiohttp.ClientSession() as session:
        async with session.get("http://127.0.0.1:5000/") as response:
            text = await response.text()
            return text

async def main():
    loop = asyncio.get_event_loop()
    # have download start "in the background"
    dltask = loop.create_task(download())
    # now sleep
    await asyncio.sleep(1)
    # and now await the end of the download
    text = await dltask

运行此协程应该花费预期的时间 .