如何遍历大型列表而不阻塞事件循环

时间:2019-04-23 13:57:36

标签: python python-3.x list asynchronous python-asyncio

我有一个带有正在运行的asyncio事件循环的python脚本,我想知道如何在不阻塞事件循环的情况下遍历大列表。从而保持循环运行。

我尝试使用__aiter____anext__创建自定义类,但该类无效,我还尝试创建async function来产生结果但仍会阻塞的自定义类。 / p>

当前:

for index, item in enumerate(list_with_thousands_of_items):
    # do something

我尝试过的自定义类:

class Aiter:
    def __init__(self, iterable):
        self.iter_ = iter(iterable)

    async def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            object = next(self.iter_)
        except StopIteration:
            raise StopAsyncIteration
        return object

但这总是导致

TypeError: 'async for' received an object from __aiter__ that does not implement __anext__: coroutine

我制作的async function有效,但仍然阻止了事件循环:

async def async_enumerate(iterable, start:int=0):
    for idx, i in enumerate(iterable, start):
        yield idx, i

2 个答案:

答案 0 :(得分:4)

正如@deceze指出的那样,您可以使用await asyncio.sleep(0)来将控制明确传递给事件循环。但是,这种方法存在问题。

估计列表很大,这就是为什么您需要采取特殊措施来解除事件循环阻塞的原因。但是,如果列表太大,则强制每次循环迭代产生事件循环将slow it down considerably。当然,您可以通过添加计数器并仅在i%10 == 0i%100 == 0等时等待来缓解这种情况。但是随后,您必须就放弃控制的频率做出任意决定(猜测)。如果您太频繁地屈服,则会降低功能。如果您很少屈服,则使事件循环无响应。

如RafaëlDera所建议,可以通过使用run_in_executor来避免这种情况。 run_in_executor接受阻塞函数,并将其执行卸载到线程池中。它立即返回可以在await中进行异步操作的Future,其结果(一旦可用)将成为阻塞函数的返回值。 (如果阻塞函数引发,则将传播异常。)此类await将暂停协程,直到该函数在其线程中返回或引发,从而使事件循环在此期间保持完整功能。由于阻塞函数和事件循环在单独的线程中运行,因此该函数无需执行任何操作即可运行事件工作-它们独立运行。在这里,即使GIL也不是问题,因为GIL确保控件在线程之间传递。

使用run_in_executor,您的代码应如下所示:

def process_the_list():
    for index, item in enumerate(list_with_thousands_of_items):
        # do something

loop = asyncio.get_event_loop()
await loop.run_in_executor(None, process_the_list)

答案 1 :(得分:3)

asyncio合作多任务处理。合作部分来自这样一个事实,即您的函数必须 yield 执行回到事件循环以允许其他事情运行。除非您await(或结束您的功能),否则您将束缚事件循环。

您可以简单地await进行一些noop事件,可能最合适的是await asyncio.sleep(0)。这样可以确保您的任务将尽快恢复,但也可以安排其他任务。