AsyncGenerator上的Async for循环

时间:2018-10-17 13:52:03

标签: python loops for-loop asynchronous python-asyncio

具有异步生成器,我希望能够异步迭代它。但是,由于最后我遇到了常规的同步for循环,因此我缺少了一些东西或弄乱了一些东西,或者两者都没有:

import asyncio


async def time_consuming(t):
    print(f"Going to sleep for {t} seconds")
    await asyncio.sleep(t)
    print(f"Slept {t} seconds")
    return t


async def generator():
    for i in range(4, 0, -1):
        yield await time_consuming(i)


async def consumer():
    async for t in generator():
        print(f"Doing something with {t}")


if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    loop.run_until_complete(consumer())
    loop.close()

这大约需要12秒钟才能运行并返回:

Going to sleep for 4 seconds
Slept 4 seconds
Doing something with 4
Going to sleep for 3 seconds
Slept 3 seconds
Doing something with 3
Going to sleep for 2 seconds
Slept 2 seconds
Doing something with 2
Going to sleep for 1 seconds
Slept 1 seconds
Doing something with 1

尽管我预计大约需要4秒钟才能运行并返回以下内容:

Going to sleep for 4 seconds
Going to sleep for 3 seconds
Going to sleep for 2 seconds
Going to sleep for 1 seconds
Slept 4 seconds
Doing something with 4
Slept 3 seconds
Doing something with 3
Slept 2 seconds
Doing something with 2
Slept 1 seconds
Doing something with 1

1 个答案:

答案 0 :(得分:4)

异步生成器并不意味着您可以同时执行迭代!您获得的全部好处是协程可以将更多的地方用于其他任务。迭代步骤仍连续运行

进行不同的放置:异步迭代器对于需要使用I / O来获取每个迭代步骤的迭代器很有用。考虑循环浏览Web套接字的结果或文件中的行。如果迭代器上的每个next()步骤都需要等待缓慢的I / O源来提供数据,那么将控制权放到已设置为可同时运行的其他位置上是一个好方法。

如果您希望生成器的每个步骤都可以同时运行,那么您仍然必须通过事件循环明确安排其他任务。

所有这些额外任务都完成后,您可以从生成器返回。如果您将4个time_consuming()协同程序安排为任务,请使用asyncio.wait()等待一个或所有任务完成,并从完成的任务中得出结果,然后在{{1}之后}循环完成后,您的过程总共只需要4秒钟:

for i in range(...):

此时输出变为

async def generator():
    pending = []
    for i in range(4, 0, -1):
        pending.append(asyncio.create_task(time_consuming(i)))

    while pending:
        done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
        for task in done:
            yield task.result()

请注意,这是预期输出的反向顺序,因为这会在完成任务结果 时接受任务结果,而不是等待创建第一个任务完成。通常,这确实是您想要的。如果您已经在1点之后准备好结果,为什么还要等待4秒钟?

您也可以使用某种形式的变体,但是您只是用不同的方式进行编码。然后,您可以只使用asyncio.gather() on the 4 tasks,它安排一堆协程作为并发任务运行,并以列表的形式返回其结果,之后您可以产生这些结果:

Going to sleep for 4 seconds
Going to sleep for 3 seconds
Going to sleep for 2 seconds
Going to sleep for 1 seconds
Slept 1 seconds
Doing something with 1
Slept 2 seconds
Doing something with 2
Slept 3 seconds
Doing something with 3
Slept 4 seconds
Doing something with 4

但是现在输出变为

async def generator():
    tasks = []
    for i in range(4, 0, -1):
        tasks.append(time_consuming(i))

    for res in await asyncio.gather(*tasks):
        yield res 

因为最长的任务Going to sleep for 4 seconds Going to sleep for 3 seconds Going to sleep for 2 seconds Going to sleep for 1 seconds Slept 1 seconds Slept 2 seconds Slept 3 seconds Slept 4 seconds Doing something with 4 Doing something with 3 Doing something with 2 Doing something with 1 完成之前我们无法做任何事情,而运行时间较短的任务在此之前完成并已经输出了time_consuming(4)消息。