2015-05-20 18 views
25

Mam następujący kod używający asyncio i aiohttp do tworzenia asynchronicznych żądań HTTP.Obsługa wyjątków asynchronicznych w języku Python

import sys 
import asyncio 
import aiohttp 

@asyncio.coroutine 
def get(url): 
    try: 
     print('GET %s' % url) 
     resp = yield from aiohttp.request('GET', url) 
    except Exception as e: 
     raise Exception("%s has error '%s'" % (url, e)) 
    else: 
     if resp.status >= 400: 
      raise Exception("%s has error '%s: %s'" % (url, resp.status, resp.reason)) 

    return (yield from resp.text()) 

@asyncio.coroutine 
def fill_data(run): 
    url = 'http://www.google.com/%s' % run['name'] 
    run['data'] = yield from get(url) 

def get_runs(): 
    runs = [ {'name': 'one'}, {'name': 'two'} ] 
    loop = asyncio.get_event_loop() 
    task = asyncio.wait([fill_data(r) for r in runs]) 
    loop.run_until_complete(task) 
    return runs 

try: 
    get_runs() 
except Exception as e: 
    print(repr(e)) 
    sys.exit(1) 

Z jakiegoś powodu wyjątki podniesione wewnątrz funkcji get nie podlegają:

Future/Task exception was never retrieved 
Traceback (most recent call last): 
    File "site-packages/asyncio/tasks.py", line 236, in _step 
    result = coro.send(value) 
    File "mwe.py", line 25, in fill_data 
    run['data'] = yield from get(url) 
    File "mwe.py", line 17, in get 
    raise Exception("%s has error '%s: %s'" % (url, resp.status, resp.reason)) 
Exception: http://www.google.com/two has error '404: Not Found' 

Więc, co jest poprawny sposób obsłużyć wyjątki zgłaszane przez couroutines?

Odpowiedz

28

asyncio.wait faktycznie nie zużywają Futures przeszedł do niego, po prostu czeka na nich do wypełnienia, a następnie zwraca Future obiekty:

współprogramasyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

Poczekaj na futures i obiekty coroutine podane przez sekwencje kontraktów futures, aby zakończyć. Coroutines będą pakowane w Zadaniach. Zwraca dwa zestawy Future: (gotowe, oczekujące).

aż faktycznie yield from pozycje na liście done, oni pozostają nieużyte. Ponieważ Twój program kończy się bez spożywania kontraktów futures, widzisz komunikat "wyjątek nigdy nie został odzyskany".

do użytku przypadku, to chyba więcej sensu używać asyncio.gather, która będzie rzeczywiście zużywają każdego Future, a następnie zwrócić pojedynczą Future który agreguje wszystkie swoje wyniki (lub budzi pierwszy Exception rzucony przez przyszłości na wejściu lista).

def get_runs(): 
    runs = [ {'name': 'one'}, {'name': 'two'} ] 
    loop = asyncio.get_event_loop() 
    tasks = asyncio.gather(*[fill_data(r) for r in runs]) 
    loop.run_until_complete(tasks) 
    return runs 

wyjściowa:

GET http://www.google.com/two 
GET http://www.google.com/one 
Exception("http://www.google.com/one has error '404: Not Found'",) 

Zauważ, że asyncio.gather faktycznie pozwala dostosować swoje zachowanie, gdy jeden z kontraktami zgłasza wyjątek; domyślne zachowanie jest podniesienie pierwszego wyjątku trafi, ale może też po prostu zwrócić każdy przedmiot wyjątku na liście wyjściowa:

asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)

Powrót przyszły sumowaniu wyników z danego współprogram sprzeciwia lub futures.

Wszystkie transakcje futures muszą współdzielić tę samą pętlę zdarzeń. Jeśli wszystkie zadania zostaną pomyślnie wykonane, wynikiem zwróconej przyszłości jest lista wyników (w kolejność oryginalnych sekwencji, niekoniecznie kolejność przylotów wyników ).Jeśli return_exceptions jest True, wyjątki w zadaniach są traktowane tak samo, jak wyniki pomyślne i zebrane na liście wyników w postaci ; w przeciwnym razie pierwszy podniesiony wyjątek zostanie natychmiast przesłany do zwróconej przyszłości.

+0

Dzięki za wyjaśnienie, Dokumentacja nie była całkowicie jasna na temat obsługi wyjątków. – megabyde

+0

Tak jak jeden zrób to z 'wait'? Czy jest to coś w rodzaju 'yield z asyncio.wait (...)'? Czy powinno się też "poczekać na asyncio.wait (...)"? – z0r

2

do debugowania lub "obsłużyć" wyjątków w callback:

współprogram które zwracają jakiś wynik lub podnieść wyjątki:

@asyncio.coroutine 
def async_something_entry_point(self): 
    try: 
     return self.real_stuff_which_throw_exceptions() 
    except: 
     raise Exception(some_identifier_here + ' ' + traceback.format_exc()) 

i oddzwanianie:

def callback(self, future: asyncio.Future): 
    exc = future.exception() 
    if exc: 
     # Handle wonderful empty TimeoutError exception 
     if type(exc) == TimeoutError: 
      self.logger('<Some id here> callback exception TimeoutError') 
     else: 
      self.logger("<Some id here> callback exception " + str(exc)) 

    # store your result where you want 
    self.result.append(
     future.result() 
    )