如何在不阻塞事件循环的情况下迭代大列表

Dyl*_*lee 5 python asynchronous list python-3.x python-asyncio

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

我试着做一个自定义类__aiter__,并__anext__没有工作,我也试着做一个async function能产生结果,但它仍然块。

目前:

for index, item in enumerate(list_with_thousands_of_items):
    # do something
Run Code Online (Sandbox Code Playgroud)

我试过的自定义类:

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
Run Code Online (Sandbox Code Playgroud)

但这总是导致

TypeError: 'async for' received an object from __aiter__ that does not implement __anext__: coroutine
Run Code Online (Sandbox Code Playgroud)

async function我做了事件循环是一种作品,但仍块:

TypeError: 'async for' received an object from __aiter__ that does not implement __anext__: coroutine
Run Code Online (Sandbox Code Playgroud)

use*_*342 6

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

大概这个列表非常大,这就是为什么你需要特殊的措施来解除事件循环的阻塞。但是如果列表如此之大,强制每个循环迭代都让步给事件循环会大大减慢它的速度。当然,您可以通过添加计数器并仅等待 wheni%10 == 0或 wheni%100 == 0等来缓解这种情况。但是,您必须就放弃控制的频率做出任意决定(猜测)。如果你太频繁地让步,你就会减慢你的功能。如果你的 yield 太少,你就会使事件循环无响应。

这可以通过使用 来避免run_in_executor,正如 RafaëlDera 所建议的那样。run_in_executor接受阻塞函数并将其执行卸载到线程池。它立即返回一个可以await在 asyncio 中编辑的未来,一旦可用,其结果将是阻塞函数的返回值。(如果阻塞函数引发,异常将改为传播。)这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)
Run Code Online (Sandbox Code Playgroud)