2015-05-21 2 views
5

У меня была гипотеза, что если бы я написал взаимно рекурсивные сопрограммы с асинчио, они не попали бы в крайнее исключение глубины рекурсии, поскольку цикл событий вызывал их (и действовал как батут). Это, однако, это не тот случай, когда я пишу их так:Взаимно рекурсивные сопрограммы с 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.

Есть ли способ удержать стек от рекурсивных сопрограмм с асинчио?

+0

каждый раз, когда вы получаете от «вы подключаетесь к следующему вызову. Вы пытались использовать очередь вместо этого? Таким образом, вы можете передать информацию и удалить ее в следующую сопрограмму, не связывая их друг с другом. – shongololo

ответ

7

Чтобы сохранить стек, вы должны разрешить каждому сопрограмме фактически выйти после того, как он планирует следующий рекурсивный вызов, а это значит, что вам нужно избегать использования yield from. Вместо этого вы используете asyncio.async (или asyncio.ensure_future, если используете Python 3.4.4+), чтобы запланировать следующую сопрограмму с циклом событий и использовать Future.add_done_callback, чтобы запланировать обратный вызов для запуска после возврата рекурсивного вызова. Затем каждый coroutine возвращает объект 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 весь путь обратно.

+0

Отличный ответ - именно то, что я искал. Благодаря! – caleb

+0

@ dano, Из любопытства. В вашем первом коде, если a и b являются бесконечными взаимными сопрограммами, которые никогда не возвращаются, это взорвет память с объектами Future. верный? Что второе? –

0

Я изменил код на async, await и измерил время. Мне очень нравится, насколько это более читаемо.

Будущее:

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 

 Смежные вопросы

  • Нет связанных вопросов^_^