简体   繁体   English

Python异步生成器不是异步

[英]Python async-generator not async

My code is as follows. 我的代码如下。 I want the two sleep can share the same time frame and take 1+2*3=7 seconds to run the script. 我希望两个睡眠可以共享相同的时间帧,并运行脚本1 + 2 * 3 = 7秒。 But it seems that something wrong happened so that it still takes 3*(1+2) second. 但似乎发生了一些错误,所以它仍然需要3 *(1 + 2)秒。

Is there any idea how to modify the code? 有没有想法如何修改代码?

import asyncio

async def g():
    for i in range(3):
        await asyncio.sleep(1)
        yield i

async def main():
    async for x in g():
        print(x)
        await asyncio.sleep(2)

loop = asyncio.get_event_loop()
res = loop.run_until_complete(main())
loop.close()

The point of async / await is to interleave tasks , not functions/generators. async / await的要点是交错任务 ,而不是函数/生成器。 For example, when you await asyncio.sleep(1) , your current coroutine is delayed along with the sleep. 例如,当您await asyncio.sleep(1) ,您当前的协程会随着睡眠而延迟。 Similarly, an async for delays its coroutine until the next item is ready. 同样, async for延迟其协程,直到下一个项目准备就绪。

In order to run your separate functionality, you must create each part as a separate task. 要运行单独的功能,必须将每个部件创建为单独的任务。 Use a Queue to exchange items between them - tasks will only be delayed until they have exchanged an item. 使用Queue在它们之间交换项目 - 任务只会延迟到交换项目为止。

from asyncio import Queue, sleep, run, gather


# the original async generator
async def g():
    for i in range(3):
        await sleep(1)
        yield i


async def producer(queue: Queue):
    async for i in g():
        print('send', i)
        await queue.put(i)  # resume once item is fetched
    await queue.put(None)


async def consumer(queue: Queue):
    x = await queue.get()  # resume once item is fetched
    while x is not None:
        print('got', x)
        await sleep(2)
        x = await queue.get()


async def main():
    queue = Queue()
    # tasks only share the queue
    await gather(
        producer(queue),
        consumer(queue),
    )


run(main())

If you regularly need this functionality, you can also put it into a helper object that wraps an asynchronous iterable. 如果您经常需要此功能,还可以将其放入包装异步迭代的辅助对象中。 The helper encapsulates the queue and separate task. 帮助程序封装了队列和单独的任务。 You can apply the helper directly on an async iterable in an async for statement. 您可以直接在async for语句中的异步迭代中应用帮助程序。

from asyncio import Queue, sleep, run, ensure_future


# helper to consume iterable as concurrent task
async def _enqueue_items(async_iterable, queue: Queue, sentinel):
    async for item in async_iterable:
        await queue.put(item)
    await queue.put(sentinel)


async def concurrent(async_iterable):
    """Concurrently fetch items from ``async_iterable``"""
    queue = Queue()
    sentinel = object()
    consumer = ensure_future(  # concurrently fetch items for the iterable
        _enqueue_items(async_iterable, queue, sentinel)
    )
    try:
        item = await queue.get()
        while item is not sentinel:
            yield item
            item = await queue.get()
    finally:
        consumer.cancel()


# the original generator
async def g():
    for i in range(3):
        await sleep(1)
        yield i


# the original main - modified with `concurrent`
async def main():
    async for x in concurrent(g()):
        print(x)
        await sleep(2)


run(main())

As an alternative to doing this with a Queue, instead this solution chain Futures together, so that a Future's result is the current item and another Future to retrieve the next item (sort of like a linked list, so to speak): 作为使用Queue执行此操作的替代方法,而是将此解决方案链连接在一起,以便Future的结果是当前项目和另一个Future来检索下一个项目(有点像链接列表,可以这么说):

from asyncio import sleep, get_event_loop, run, create_task

async def aiter(fut, async_generator):
    try:
        async for item in async_generator:
            fut, prev_fut = get_event_loop().create_future(), fut
            prev_fut.set_result((item, fut))
        else:
            fut.set_exception(StopAsyncIteration())
    except Exception as e:
        fut.set_exception(e)


async def concurrent(async_generator):
    fut = get_event_loop().create_future()
    create_task(aiter(fut, async_generator))

    try:
        while True:
            item, fut = await fut
            yield item
    except StopAsyncIteration as e:
        return

As an added bonus this solution will correctly handle exception that happens in g() by reraising the exception in the main() method with a traceback that will be useful for debugging. 作为一个额外的好处,这个解决方案将通过在main()方法中使用对调试有用的回溯来重新引用异常来正确处理g()中发生的异常。

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

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