asyncio + aiohttp:与睡眠重叠的 IO
asyncio + aiohttp: overlapping IO with sleeping
当所有协程都在等待时,asyncio 监听事件以再次唤醒它们。一个常见的例子是 asyncio.sleep()
,它注册了一个定时事件。实际上,事件通常是准备好接收或发送新数据的 IO 套接字。
为了更好地理解这种行为,我设置了一个简单的测试:它向本地主机发送一个 http 请求并等待响应。在本地主机上,我设置了一个烧瓶服务器,它在响应前等待 1 秒。发送请求后,客户端休眠 1 秒,然后等待响应。我希望它在大约一秒钟内达到 return,因为我的程序和服务器都应该并行休眠。但是需要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
asyncio 在这里做什么,为什么我不能重叠等待时间?
我对HTTP请求的具体场景不感兴趣,aiohttp只是用来构造一个例子。这可能有点危险:这可能与 aiohttp 有关,而与 asyncio 无关。
实际上,我希望是这种情况(因此出现了关于 asyncio 和 aiohttp 的问题标题)。
我的第一直觉是请求可能在调用 asyncio.sleep().
之前没有发送,所以我重新排序了一下:
# 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 秒。服务器在客户端运行后立即打印incoming。客户端总共耗时11秒。
@app.route('/')
def index():
print("incoming")
time.sleep(1)
return 'done'
由于HTTP请求是立即发出的,所以在客户端从asyncio.sleep().
醒来之前,服务器肯定已经发送了一个应答asyncio.sleep().
在我看来,提供HTTP请求的套接字应该在客户醒来。但是,总 运行 时间总是加上客户端和服务器等待时间。
我是不是以某种方式滥用了 asyncio,还是这毕竟与 aiohttp 有关?
问题是服务器中发生的一秒钟是在 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
...
顺便说一句,只要您有另一个 运行 协同程序,您肯定可以重叠这个等待时间。
您的测试代码有三个等待(两个显式等待,一个隐藏在 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
运行 这个协程应该花费预期的时间。
当所有协程都在等待时,asyncio 监听事件以再次唤醒它们。一个常见的例子是 asyncio.sleep()
,它注册了一个定时事件。实际上,事件通常是准备好接收或发送新数据的 IO 套接字。
为了更好地理解这种行为,我设置了一个简单的测试:它向本地主机发送一个 http 请求并等待响应。在本地主机上,我设置了一个烧瓶服务器,它在响应前等待 1 秒。发送请求后,客户端休眠 1 秒,然后等待响应。我希望它在大约一秒钟内达到 return,因为我的程序和服务器都应该并行休眠。但是需要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
asyncio 在这里做什么,为什么我不能重叠等待时间?
我对HTTP请求的具体场景不感兴趣,aiohttp只是用来构造一个例子。这可能有点危险:这可能与 aiohttp 有关,而与 asyncio 无关。
实际上,我希望是这种情况(因此出现了关于 asyncio 和 aiohttp 的问题标题)。
我的第一直觉是请求可能在调用 asyncio.sleep().
之前没有发送,所以我重新排序了一下:
# 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 秒。服务器在客户端运行后立即打印incoming。客户端总共耗时11秒。
@app.route('/')
def index():
print("incoming")
time.sleep(1)
return 'done'
由于HTTP请求是立即发出的,所以在客户端从asyncio.sleep().
醒来之前,服务器肯定已经发送了一个应答asyncio.sleep().
在我看来,提供HTTP请求的套接字应该在客户醒来。但是,总 运行 时间总是加上客户端和服务器等待时间。
我是不是以某种方式滥用了 asyncio,还是这毕竟与 aiohttp 有关?
问题是服务器中发生的一秒钟是在 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
...
顺便说一句,只要您有另一个 运行 协同程序,您肯定可以重叠这个等待时间。
您的测试代码有三个等待(两个显式等待,一个隐藏在 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
运行 这个协程应该花费预期的时间。