繁体   English   中英

可以从事件循环中返回异步生成器数据吗?

[英]Yielding asyncio generator data back from event loop possible?

我想使用httpx从协程内的多个同时 HTTP 流请求中读取数据,并将数据返回给运行事件循环的非异步 function ,而不仅仅是返回最终数据。

但是,如果我让我的异步函数 yield 而不是 return,我会抱怨asyncio.as_completed()loop.run_until_complete()需要协程或 Future,而不是异步生成器。

所以我可以让它工作的唯一方法是收集每个协程内的所有流数据,一旦请求完成就返回所有数据。 然后收集所有协程结果,最后将其返回给非异步调用 function。

这意味着我必须将所有内容保留在 memory 中,并等到最慢的请求完成后才能获得所有数据,这与流式传输 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.Queuetrio memory 通道的解决方案,但是由于我只能从异步 scope 中的那些通道中读取它,它并没有让我更接近解决方案

EDIT 2 : The reason I want to use this from a non-asyncronous generator is that I want to use it from a Django app using a Django Rest Framework streaming API.

通常你应该只使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__魔术方法不断从异步迭代器中检索值,并在对象到达时返回它们。 这个异步闭包是在普通同步生成器内的循环中使用run_until_complete()调用的。 (封闭件实际上返回一对处理完毕指示器和实际的对象的,以避免传播StopAsyncIteration通过run_until_complete ,这可能是不支持的。)

有了这个,您可以使您的execute_tasks成为异步生成器( async def with yield )并使用以下方法对其进行迭代:

for chunk in iter_over_async(execute_tasks(urls), loop):
    ...

请注意,这种方法与asyncio.run不兼容,并且可能会在以后导致问题。

只是想更新@user4815162342 的解决方案以使用asyncio.run_coroutine_threadsafe而不是loop.run_until_complete

import asyncio
from typing import Any, AsyncGenerator

def _iter_over_async(loop: asyncio.AbstractEventLoop, async_generator: AsyncGenerator):
    ait = async_generator.__aiter__()

    async def get_next() -> tuple[bool, Any]:
        try:
            obj = await ait.__anext__()
            done = False

        except StopAsyncIteration:
            obj = None
            done = True

        return done, obj

    while True:
        done, obj = asyncio.run_coroutine_threadsafe(get_next(), loop).result()

        if done:
            break

        yield obj

我还想补充一点,我发现这样的工具在将同步代码分段转换为异步代码的过程中非常有用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM