首页 文章

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

提问于
浏览
6

我得出的假设是,如果我用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))

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

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

2 回答

  • 0

    为了防止堆栈增长,您必须允许每个协程在调度下一个递归调用后实际退出,这意味着您必须避免使用 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
    

    现在,您的示例代码实际上并没有返回 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()
    

    但是我怀疑你真的想要一直回归 n .

  • 8

    我将代码更改为 asyncawait 和测量时间 . 我真的很喜欢它的可读性 .

    未来:

    import asyncio
    
    @asyncio.coroutine
    def a(n):
        fut = asyncio.Future()
        def set_result(out):
            fut.set_result(out.result())
        if n > 1000: 
            return n
        else: 
            in_fut = asyncio.async(b(n+1))
            in_fut.add_done_callback(set_result)
        return fut
    
    @asyncio.coroutine
    def b(n):
        fut = asyncio.Future()
        def set_result(out):
            fut.set_result(out.result())
        in_fut = asyncio.async(a(n+1))
        in_fut.add_done_callback(set_result)
        return fut
    
    import timeit
    print(min(timeit.repeat("""
    loop = asyncio.get_event_loop()
    loop.run_until_complete(a(0))
    """, "from __main__ import a, b, asyncio", number=10)))
    

    结果:

    % time python stack_ori.py 
    0.6602963969999109
    python stack_ori.py  2,06s user 0,01s system 99% cpu 2,071 total
    

    异步,等待:

    import asyncio
    
    async def a(n):
        if n > 1000:
            return n
        else:
            ret = await asyncio.ensure_future(b(n + 1))
        return ret
    
    async def b(n):
        ret = await asyncio.ensure_future(a(n + 1))
        return ret
    
    import timeit
    print(min(timeit.repeat("""
    loop = asyncio.get_event_loop()
    loop.run_until_complete(a(0))
    """, "from __main__ import a, b, asyncio", number=10)))
    

    结果:

    % time  python stack.py
    0.45157229300002655
    python stack.py  1,42s user 0,02s system 99% cpu 1,451 total
    

相关问题