簡體   English   中英

Python異步生成器不是異步

[英]Python async-generator not async

我的代碼如下。 我希望兩個睡眠可以共享相同的時間幀,並運行腳本1 + 2 * 3 = 7秒。 但似乎發生了一些錯誤,所以它仍然需要3 *(1 + 2)秒。

有沒有想法如何修改代碼?

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()

async / await的要點是交錯任務 ,而不是函數/生成器。 例如,當您await asyncio.sleep(1) ,您當前的協程會隨着睡眠而延遲。 同樣, async for延遲其協程,直到下一個項目准備就緒。

要運行單獨的功能,必須將每個部件創建為單獨的任務。 使用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())

如果您經常需要此功能,還可以將其放入包裝異步迭代的輔助對象中。 幫助程序封裝了隊列和單獨的任務。 您可以直接在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())

作為使用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

作為一個額外的好處,這個解決方案將通過在main()方法中使用對調試有用的回溯來重新引用異常來正確處理g()中發生的異常。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM