8.6. Excepții¶
Excepțiile din interiorul unui script asyncio se comportă aproape la fel ca în Python obișnuit – se propagă în sus pe lanțul de apeluri până când ceva le captează. Aproape, deoarece sarcinile rulează în paralel, așa că drumul „în sus” nu este drumul care a creat sarcina. Această pagină tratează unde ajung excepțiile în fiecare dintre formele uzuale.
8.6.1. În interiorul unei singure corutine¶
Un try/except din interiorul unei corutine captează excepțiile ridicate de orice așteaptă cu await, în modul obișnuit:
async def fetch_with_retry(url):
for attempt in range(3):
try:
return await fetch(url)
except OSError as e:
last_error = e
raise last_error
Nimic specific asyncio aici – clauza except vede excepțiile ridicate în interiorul fetch ca și cum ar fi fost un apel de funcție obișnuit.
8.6.2. Într-o sarcină pe care aplicația o așteaptă¶
Când o corutină care rulează ca Task ridică o excepție, aceasta este stocată în sarcină. Următoarea dată când ceva așteaptă acea sarcină cu await, excepția este reridiată la await
task = asyncio.create_task(may_fail())
try:
result = await task
except OSError:
log("may_fail failed")
Același lucru se aplică pentru asyncio.gather(). Comportamentul implicit – un copil ridică o excepție, ceilalți sunt anulați, excepția se propagă în afara gather-ului – provine din acest mecanism.
8.6.3. Într-o sarcină pe care nimeni nu o așteaptă¶
O sarcină pe care nimeni nu o așteaptă vreodată cu await este cazul care necesită atenție. Excepția se produce totuși; bucla observă că sarcina s-a încheiat cu o excepție netratată; dar nu există niciun await la care aceasta să apară. Comportamentul implicit este de a afișa un traceback prin sys.stderr și de a continua execuția – ceea ce este în regulă pentru un diagnostic nesupravegheat, dar nepotrivit pentru o aplicație care ar fi vrut să afle.
Soluția corectă este de obicei să aștepți sarcina. Fie direct, reținând referința și așteptând-o în timpul opririi, fie implicit prin gather() sau wait_for(). Tiparul „oprirea unei aplicații” de pe pagina Expirări și anulare tratează acest caz pentru sarcinile de fundal de lungă durată pe care le creează un script tipic.
8.6.4. Gestionar de excepții personalizat¶
Când tracebackul-silențios-și-continuă nu este suficient, bucla expune un cârlig – Loop.set_exception_handler – pe care aplicația îl poate suprascrie pentru a face altceva:
def handler(loop, context):
print("asyncio:", context.get("message"))
if "exception" in context:
sys.print_exception(context["exception"])
loop = asyncio.get_event_loop()
loop.set_exception_handler(handler)
Argumentul context este un dicționar cu cheile 'message', 'exception' și 'future'. Excepția poate lipsi în cazul anumitor evenimente de tip avertisment, motiv pentru care exemplul folosește .get().
Utilizările tipice sunt înregistrarea eșecului în memoria flash, aprinderea intermitentă a unui LED de eroare sau escaladarea către o repornire prin watchdog. Pagina controlul buclei tratează întreaga suprafață a cârligelor buclei.
8.6.5. KeyboardInterrupt¶
Când un script este oprit din exterior – de obicei de către IDE care îi cere să se oprească – cererea ajunge în interiorul scriptului sub forma unei KeyboardInterrupt. În interiorul asyncio.run(), aceasta se propagă la fel ca orice altă excepție netratată: main este anulat, fiecare sarcină pe care bucla o urmărește este de asemenea anulată, iar KeyboardInterrupt este reridiată în afara asyncio.run(). Clauzele finally se execută la ieșire, așa că același tipar de curățare de pe pagina despre anulare este cel care o tratează.