2015-05-21 32 views
5

Miałem hipotezę, że jeśli napiszę rekurencyjne rekurencyjne współprogramy z asynczem, to nie trafią one w wyjątek maksymalnej głębokości rekursji, ponieważ pętla zdarzeń je wywoływała (i zachowywała się jak trampolina). To jednak nie jest to przypadek, gdy piszę je tak:Wzajemnie rekurencyjni współprowadzący z 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)) 

Gdy skończy, mam RuntimeError: maximum recursion depth exceeded while calling a Python object.

Czy istnieje sposób na utrzymanie stosu w rekurencyjnych coroutines z asyncio?

+0

za każdym razem, gdy "ustąpisz", podpinacie się do następnego połączenia. Czy próbowałeś użyć kolejki? W ten sposób możesz przekazać informacje i zapisać je w kolejnym korupcji bez łączenia ich ze sobą. – shongololo

Odpowiedz

7

Aby utrzymać wzrost stosu, należy zezwolić na to, aby każdy z nich rzeczywiście zakończył działanie po zaplanowaniu następnego wywołania rekursywnego, co oznacza, że ​​należy unikać używania yield from. Zamiast tego, używasz asyncio.async (lub asyncio.ensure_future, jeśli używasz Python 3.4.4+), aby zaplanować następną coroutine z pętlą zdarzeń, i użyć Future.add_done_callback, aby zaplanować wywołanie zwrotne do uruchomienia po wywołaniu rekursywnego. Każdy coroutine następnie zwraca obiekt asyncio.Future, który ma swój zestaw wyników wewnątrz wywołania zwrotnego, które jest uruchamiane, gdy zaplanowane wywołanie rekurencyjne zostanie ukończone.

To chyba najłatwiejszy do zrozumienia, jeśli rzeczywiście zobaczyć kod:

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 

Teraz Twój przykładowy kod faktycznie nie wrócić n całą drogę z powrotem na stos, więc można zrobić coś funkcjonalnie równoważne to nieco prostsze:

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() 

Ale podejrzewam, że naprawdę oznaczało powrót n całą drogę z powrotem do góry.

+0

Świetna odpowiedź - dokładnie to, czego szukałem. Dzięki! – caleb

+0

@dano, Z ciekawości. W twoim pierwszym kodzie, jeśli aib są nieskończonymi wzajemnymi współprogramami, które nigdy nie powrócą, spowoduje to eksplozję pamięci za pomocą obiektów Future. poprawny? Co jest w drugim przypadku? –

0

Zmieniłem kod na async, await i zmierzono czas. Bardzo podoba mi się to, o ile jest czytelniejszy.

Future:

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))) 

Wynik:

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

asynchroniczny czekają:

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))) 

Wynik:

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