我正在使用Python 3 asyncio框架评估不同的模式以定期执行(实际的睡眠/延迟为了简洁),我有两段代码表现得很差,我无法解释原因。正如我所预料的那样,第一个版本使用yield from
来递归调用自身,在大约1000次迭代中耗尽了堆栈。第二个版本以递归方式调用协程,但将实际的事件循环执行委托给asyncio.async
并且不会耗尽堆栈。你能详细解释为什么第二个版本没有使用堆栈吗?执行此协程的两种方式有什么区别?
第一个版本(收益率):
@asyncio.coroutine
def call_self(self, i):
print('calling self', i)
yield from self.call_self(i + 1)
第二个版本(asyncio.async):
@asyncio.coroutine
def call_self(self, i):
print('calling self', i)
asyncio.async(self.call_self(i + 1))
答案 0 :(得分:11)
使用yield from
的第一个示例实际上阻止call_self
的每个实例,直到它对call_self
的递归调用返回。这意味着调用堆栈会持续增长,直到用完堆栈空间。正如你所提到的,这是显而易见的行为。
使用asyncio.async
的第二个示例不会阻止任何地方。因此,call_self
的每个实例在运行asyncio.async(...)
后立即退出,这意味着堆栈不会无限增长,这意味着您不会耗尽堆栈。相反,asyncio.async
计划call_self
将在事件循环的迭代中执行,方法是将其包装在asyncio.Task
中。
以下是__init__
的{{1}}:
Task
对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
实例的上下文。