8.6. Kivételek¶
Az asyncio szkripten belüli kivételek szinte ugyanúgy viselkednek, mint a szokásos Pythonban – felfelé terjednek a hívási láncon, amíg valami el nem kapja őket. Szinte, mert a feladatok párhuzamosan futnak, így a „felfelé” út nem azonos azzal az úttal, amely a feladatot létrehozta. Ez az oldal tárgyalja, hogy a kivételek hová kerülnek az egyes gyakori formákban.
8.6.1. Egyetlen korutinon belül¶
Egy korutinon belüli try/except a szokásos módon elkapja a bármi által kiváltott kivételeket, amit await-tel megvár:
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
Itt semmi asyncio-specifikus nincs – az except ág úgy látja a fetch belsejében kiváltott kivételeket, mintha az egy szokásos függvényhívás lett volna.
8.6.2. Egy feladatban, amelyet az alkalmazás megvár¶
Amikor egy Task feladatként futó korutin kivételt vált ki, a kivétel a feladaton tárolódik. Amikor legközelebb valami await-tel megvárja azt a feladatot, a kivétel újra kiváltódik az await helyén:
task = asyncio.create_task(may_fail())
try:
result = await task
except OSError:
log("may_fail failed")
Ugyanez vonatkozik az asyncio.gather() hívásra is. Az alapértelmezett viselkedés – az egyik gyermek kivételt vált ki, a többi megszakad, a kivétel pedig kiterjed a gather hívásból – ebből a mechanizmusból ered.
8.6.3. Egy feladatban, amelyet senki sem vár meg¶
Egy feladat, amelyet soha senki sem vár meg await-tel, az az eset, amely figyelmet igényel. A kivétel ettől még megtörténik; a hurok észreveszi, hogy a feladat kezeletlen kivétellel fejeződött be; de nincs await, ahol felszínre kerülhetne. Az alapértelmezett viselkedés az, hogy egy visszakövetést ír ki a sys.stderr csatornán, és tovább fut – ami egy felügyelet nélküli diagnosztikához rendben van, de gyenge megoldás egy olyan alkalmazáshoz, amely tudni akart róla.
A helyes megoldás általában az, hogy megvárjuk a feladatot. Vagy közvetlenül, a kezelő megjegyzésével és a leállítás során való megvárásával, vagy közvetetten az gather() vagy az wait_for() révén. A Időtúllépések és megszakítás oldal „egy alkalmazás leállítása” mintája elkapja ezt az esetet a hosszú életű háttérfeladatoknál, amelyeket egy tipikus szkript indít.
8.6.4. Egyéni kivételkezelő¶
Amikor a néma visszakövetés-és-folytatás nem elegendő, a hurok kínál egy horgot – Loop.set_exception_handler –, amelyet az alkalmazás felülírhat, hogy valami mást tegyen:
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)
A context argumentum egy szótár, amelynek kulcsai 'message', 'exception' és 'future'. A kivétel hiányozhat bizonyos figyelmeztetés-jellegű eseményeknél, ezért használja a példa a .get() hívást.
Tipikus felhasználások a hiba flash memóriába naplózása, egy hiba-LED villogtatása, vagy egy felügyeleti újraindításra való eszkaláció. A hurokvezérlés oldal a hurokhorgok teljes felületét tárgyalja.
8.6.5. KeyboardInterrupt¶
Amikor egy szkriptet kívülről állítanak le – általában úgy, hogy az IDE leállítást kér –, a kérés a szkripten belül egy KeyboardInterrupt formájában érkezik meg. Az asyncio.run() belsejében ugyanúgy terjed, mint bármely más kezeletlen kivétel: a main megszakad, minden feladat, amelyet a hurok nyomon követ, szintén megszakad, és a KeyboardInterrupt újra kiváltódik az asyncio.run() hívásból. A finally ágak lefutnak a kilépés során, így ugyanaz a tisztítási minta kezeli ezt, mint a megszakítási oldalon.