4

我正在使用 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))
4

1 回答 1

11

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.

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.

Here's the __init__ for 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)

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.

于 2014-10-26T22:19:23.817 回答