我有一个带有正在运行的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
答案 0 :(得分:4)
正如@deceze指出的那样,您可以使用await asyncio.sleep(0)
来将控制明确传递给事件循环。但是,这种方法存在问题。
估计列表很大,这就是为什么您需要采取特殊措施来解除事件循环阻塞的原因。但是,如果列表太大,则强制每次循环迭代产生事件循环将slow it down considerably。当然,您可以通过添加计数器并仅在i%10 == 0
或i%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)
。这样可以确保您的任务将尽快恢复,但也可以安排其他任务。