2015-05-21 39 views
5

Tôi đã có giả thuyết rằng nếu tôi đã viết các coroutine đệ quy cùng với asyncio, chúng sẽ không đạt tới độ sâu đệ quy tối đa, vì vòng lặp sự kiện gọi chúng (và hoạt động như tấm bạt lò xo). Tuy nhiên, đây không phải là trường hợp khi tôi viết chúng như thế này:Loài đệ quy đệ quy với 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)) 

Khi điều này chạy, tôi nhận được RuntimeError: maximum recursion depth exceeded while calling a Python object.

Có cách nào để ngăn không cho ngăn xếp phát triển trong coroutines đệ quy với asyncio không?

+0

mỗi lần bạn 'thu được từ' bạn đang tìm trong cuộc gọi tiếp theo. Bạn đã thử sử dụng hàng đợi chưa? Bằng cách đó bạn có thể đưa thông tin và dequeue vào coroutine tiếp theo mà không liên kết chúng với nhau. – shongololo

Trả lời

7

Để ngăn ngăn xếp phát triển, bạn phải cho phép mỗi coroutine thực sự thoát sau khi nó lên lịch cuộc gọi đệ quy tiếp theo, có nghĩa là bạn phải tránh sử dụng yield from. Thay vào đó, bạn sử dụng asyncio.async (hoặc asyncio.ensure_future nếu sử dụng Python 3.4.4+) để lên lịch cho coroutine tiếp theo bằng vòng lặp sự kiện và sử dụng Future.add_done_callback để lên lịch gọi lại để chạy khi trả về cuộc gọi đệ quy. Mỗi coroutine sau đó trả về một đối tượng asyncio.Future, trong đó có kết quả của nó được thiết lập bên trong gọi lại đang chạy khi cuộc gọi đệ quy mà nó đã lên lịch hoàn thành.

Có lẽ đơn giản nhất để hiểu được nếu bạn thực sự thấy mã:

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 

Bây giờ, ví dụ mã của bạn không thực sự trở lại n tất cả các cách trở lại lên stack, vì vậy bạn có thể làm một cái gì đó chức năng tương đương đó là đơn giản hơn một chút:

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

Nhưng tôi nghi ngờ bạn thực sự muốn trả lại n tất cả các cách sao lưu.

+0

Câu trả lời hay - chính xác những gì tôi đang tìm kiếm. Cảm ơn! – caleb

+0

@dano, Hết sức tò mò. Trong mã đầu tiên của bạn, nếu a và b là coroutines vô hạn lẫn nhau mà không bao giờ quay trở lại, điều này sẽ phát nổ bộ nhớ với các đối tượng trong tương lai. chính xác? Cái gì đứng thứ hai? –

0

Tôi đã thay đổi mã thành async, await và thời gian đo. Tôi thực sự thích nó dễ đọc hơn bao nhiêu.

tương lai:

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

Kết quả:

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

Async, chờ đợi:

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

Kết quả:

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