我想使用httpx从协程内部读取多个同时的HTTP流请求,然后将数据返回运行事件循环的非异步函数中,而不是仅仅返回最终数据。
但是,如果我使异步函数产生而不是返回,我会抱怨asyncio.as_completed()
和loop.run_until_complete()
期望协程或Future,而不是异步生成器。
因此,使此方法完全起作用的唯一方法是收集每个协程内部的所有流数据,并在请求完成后返回所有数据。然后收集所有协程结果,最后将其返回给非异步调用函数。
这意味着我必须将所有内容都保留在内存中,并等到最慢的请求完成,然后才能获取所有数据,这破坏了流HTTP请求的全部目的。
有什么办法可以完成这样的事情吗?我当前的愚蠢实现看起来像这样:
def collect_data(urls):
"""Non-async function wishing it was a non-async generator"""
async def stream(async_client, url, payload):
data = []
async with async_client.stream("GET", url=url) as ar:
ar.raise_for_status()
async for line in ar.aiter_lines():
data.append(line)
# would like to yield each line here
return data
async def execute_tasks(urls):
all_data = []
async with httpx.AsyncClient() as async_client:
tasks = [stream(async_client, url) for url in urls]
for coroutine in asyncio.as_completed(tasks):
all_data += await coroutine
# would like to iterate and yield each line here
return all_events
try:
loop = asyncio.get_event_loop()
data = loop.run_until_complete(execute_tasks(urls=urls))
return data
# would like to iterate and yield the data here as it becomes available
finally:
loop.close()
编辑:我也尝试过使用asyncio.Queue
和trio
内存通道的一些解决方案,但是由于我只能从异步作用域中的内容中读取内容,因此不会不能让我更接近解决方案
编辑2 :之所以要在非异步生成器中使用它,是因为我想在使用Django Rest Framework流API的Django应用中使用它。
答案 0 :(得分:2)
通常,您应该只使collect_data
异步,并在整个过程中使用异步代码-这就是设计使用asyncio的方式。但是,如果由于某种原因这不可行,您可以 通过应用一些粘合代码手动迭代异步迭代器:
def iter_over_async(ait, loop):
ait = ait.__aiter__()
async def get_next():
try:
obj = await ait.__anext__()
return False, obj
except StopAsyncIteration:
return True, None
while True:
done, obj = loop.run_until_complete(get_next())
if done:
break
yield obj
以上方法的工作方式是提供一个异步闭包,该闭包将继续使用__anext__
magic method从异步迭代器中检索值,并在它们到达时返回对象。在普通同步生成器内部的循环中,使用run_until_complete()
调用此异步关闭。 (闭包实际上返回一对已完成的指示器和实际对象,以避免传播StopAsyncIteration
到run_until_complete
,这可能不受支持。)
使用此功能后,您可以将execute_tasks
设为异步生成器(async def
与yield
)并使用以下方法对其进行迭代:
for chunk in iter_over_async(execute_tasks(urls), loop):
...
请注意,此方法与asyncio.run
不兼容,可能在以后产生问题。