Python 3 asyncio-vs asyncio的收益 [英] Python 3 asyncio - yield from vs asyncio.async stack usage
问题描述
我正在使用Python 3 asyncio框架评估定期执行的不同模式(为简洁起见,省略了实际的睡眠/延迟),并且我有两段代码表现不同,我无法解释原因。如我所料,第一个版本使用 yield from
来调用自身,从而在大约1000次迭代中递归耗尽了堆栈。第二个版本递归调用协程,但是将实际的事件循环执行委托给 asyncio.async
,并且不会耗尽堆栈。您能否详细说明第二个版本为什么不使用该堆栈?执行此协程的两种方式之间有什么区别?
I'm evaluating different patterns for periodic execution (actual sleep/delays ommited for brevity) using the Python 3 asyncio framework, and I have two pieces of code that behave diffrently and I can't explain why. The first version, which uses yield from
to call itself recursively exhausts the stack in about 1000 iterations, as I expected. The second version calls the coroutine recursively, but delegates actual event loop execution to asyncio.async
and doesn't exhaust the stack. Can you explain in detail why the stack isn't being used by the second version? What are the differences between the two ways of executing this coroutine?
第一个版本(从中产生):
First version (yield from):
@asyncio.coroutine
def call_self(self, i):
print('calling self', i)
yield from self.call_self(i + 1)
第二版(asyncio.async):
Second version (asyncio.async):
@asyncio.coroutine
def call_self(self, i):
print('calling self', i)
asyncio.async(self.call_self(i + 1))
推荐答案
第一个例如,使用 yield from
,实际上阻止了 call_self
的每个实例,直到对 call_self的递归调用为止
返回。这意味着调用堆栈会不断增长,直到堆栈空间用完为止。如您所提到的,这是显而易见的行为。
The first example, using yield from
, actually blocks each instance of call_self
until its recursive call to call_self
returns. This means the call stack keeps growing until you run out of stack space. As you mentioned, this is the obvious behavior.
第二个示例使用 asyncio.async
并没有封锁任何地方。因此,每个 call_self
的实例在运行 asyncio.async(...)
后立即退出,这意味着堆栈不会不会无限增长,这意味着您不会耗尽堆栈。相反, asyncio.async
调度 call_self
可以在事件循环的迭代中执行,方法是将其包装在 asyncio.Task
。
The second example, using asyncio.async
, doesn't block anywhere. So, each instance of call_self
immediately exits after running asyncio.async(...)
, which means the stack doesn't grow infinitely, which means you don't exhaust the stack. Instead, asyncio.async
schedules call_self
gets to be executed on the iteration of the event loop, by wrapping it in a asyncio.Task
.
这是 __ init __
的任务
:
def __init__(self, coro, *, loop=None):
assert iscoroutine(coro), repr(coro) # Not a coroutine function!
super().__init__(loop=loop)
self._coro = iter(coro) # Use the iterator just in case.
self._fut_waiter = None
self._must_cancel = False
self._loop.call_soon(self._step) # This schedules the coroutine to be run
self.__class__._all_tasks.add(self)
对 self._loop.call_soon(self。 _step)
是使协程实际执行的原因。因为它是以非阻塞方式发生的,所以 call_self
的调用堆栈永远不会超出对 Task
构造函数的调用。 。然后,下一个 call_self
的下一个实例就由事件循环在下一次迭代中启动(该循环从上一个 call_self $ c $开始c>假定事件循环中没有其他任何运行,则返回),完全在先前
call_self
实例的上下文之外。
The call to self._loop.call_soon(self._step)
is what actually makes the coroutine execute. Because it's happening in a non-blocking way, the call stack from call_self
never grows beyond the call to the Task
constructor. Then the next instance of call_self
gets kicked off by the event loop on its next iteration (which starts as soon as the previous call_self
returns, assuming nothing else is running in the event loop), completely outside of the context of the previous call_self
instance.
这篇关于Python 3 asyncio-vs asyncio的收益的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!