使用asyncio进行相互递归的协同程序

cal*_*leb 6 python recursion python-asyncio

我得出的假设是,如果我用asyncio编写相互递归的协同程序,它们就不会达到最大递归深度异常,因为事件循环正在调用它们(并且像蹦床一样).但是,当我这样写它时,情况并非如此:

import asyncio

@asyncio.coroutine
def a(n):
    print("A: {}".format(n))
    if n > 1000: return n
    else: yield from b(n+1)

@asyncio.coroutine
def b(n):
    print("B: {}".format(n))
    yield from a(n+1)

loop = asyncio.get_event_loop()
loop.run_until_complete(a(0))
Run Code Online (Sandbox Code Playgroud)

当这个运行时,我明白了RuntimeError: maximum recursion depth exceeded while calling a Python object.

有没有办法防止堆栈在使用asyncio的递归协同程序中增长?

dan*_*ano 8

为了防止堆栈增长,您必须允许每个协程在调度下一个递归调用后实际退出,这意味着您必须避免使用yield from.相反,您使用asyncio.async(或者asyncio.ensure_future如果使用Python 3.4.4+)使用事件循环来安排下一个协同程序,并使用Future.add_done_callback一个回调函数在递归调用返回时调度运行.然后每个协同程序返回一个asyncio.Future对象,该对象的结果集在回调中,当它调度的递归调用完成时运行.

如果您真正看到代码,可能最容易理解:

import asyncio

@asyncio.coroutine
def a(n):
    fut = asyncio.Future()  # We're going to return this right away to our caller
    def set_result(out):  # This gets called when the next recursive call completes
        fut.set_result(out.result()) # Pull the result from the inner call and return it up the stack.
    print("A: {}".format(n))
    if n > 1000: 
        return n
    else: 
        in_fut = asyncio.async(b(n+1))  # This returns an asyncio.Task
        in_fut.add_done_callback(set_result) # schedule set_result when the Task is done.
    return fut

@asyncio.coroutine
def b(n):
    fut = asyncio.Future()
    def set_result(out):
        fut.set_result(out.result())
    print("B: {}".format(n))
    in_fut = asyncio.async(a(n+1))
    in_fut.add_done_callback(set_result)
    return fut

loop = asyncio.get_event_loop()
print("Out is {}".format(loop.run_until_complete(a(0))))


Output:
A: 0
B: 1
A: 2
B: 3
A: 4
B: 5
...
A: 994
B: 995
A: 996
B: 997
A: 998
B: 999
A: 1000
B: 1001
A: 1002
Out is 1002
Run Code Online (Sandbox Code Playgroud)

现在,您的示例代码实际上并没有n一直返回堆栈,因此您可以创建功能相同的东西,这样做会更简单:

import asyncio

@asyncio.coroutine
def a(n):
    print("A: {}".format(n))
    if n > 1000: loop.stop(); return n
    else: asyncio.async(b(n+1))

@asyncio.coroutine
def b(n):
    print("B: {}".format(n))
    asyncio.async(a(n+1))

loop = asyncio.get_event_loop()
asyncio.async(a(0))
loop.run_forever()
Run Code Online (Sandbox Code Playgroud)

但我怀疑你真的想要n一路回来.