8.6. Výjimky¶
Výjimky uvnitř skriptu s asyncio se chovají téměř stejně jako v běžném Pythonu – propagují se nahoru řetězcem volání, dokud je něco nezachytí. Téměř proto, že úlohy běží paralelně, takže cesta „nahoru“ není cestou, která úlohu vytvořila. Tato stránka popisuje, kam výjimky míří v každé z běžných podob.
8.6.1. Uvnitř jedné korutiny¶
try/except uvnitř korutiny zachytí výjimky vyvolané čímkoli, na co se awaituje, obvyklým způsobem:
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
Není zde nic specifického pro asyncio – klauzule except vidí výjimky vyvolané uvnitř fetch, jako by šlo o běžné volání funkce.
8.6.2. V úloze, na kterou aplikace čeká¶
Když korutina běžící jako Task vyvolá výjimku, výjimka se uloží do úlohy. Při příštím awaitu této úlohy se výjimka znovu vyvolá u await
task = asyncio.create_task(may_fail())
try:
result = await task
except OSError:
log("may_fail failed")
Totéž platí pro asyncio.gather(). Výchozí chování – jeden potomek vyvolá výjimku, ostatní jsou zrušeni, výjimka se propaguje ven z gather – pochází právě z tohoto mechanismu.
8.6.3. V úloze, na kterou nikdo nečeká¶
Úloha, na kterou nikdo nikdy awaituje, je případ, který vyžaduje pozornost. Výjimka přesto nastane; smyčka zaznamená, že úloha skončila s neošetřenou výjimkou; ale není žádný await, u kterého by se mohla projevit. Výchozím chováním je vypsat trasování zásobníku přes sys.stderr a pokračovat v běhu – což je v pořádku pro bezobslužnou diagnostiku, ale špatně se hodí pro aplikaci, která to chtěla vědět.
Správnou nápravou je obvykle počkat na úlohu. Buď přímo, zapamatováním si handle a počkáním na něj během vypínání, nebo implicitně přes gather() či wait_for(). Vzor „vypínání aplikace“ ze stránky Timeouty a rušení zachytává tento případ pro dlouho žijící úlohy na pozadí, které typický skript spouští.
8.6.4. Vlastní obsluha výjimek¶
Když tiché vypsání trasování a pokračování nestačí, smyčka vystavuje hook – Loop.set_exception_handler – který může aplikace přepsat, aby udělala něco jiného:
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)
Argument context je slovník s klíči 'message', 'exception' a 'future'. U určitých událostí typu varování může výjimka chybět, a proto příklad používá .get().
Typickým použitím je zaznamenat selhání do flash paměti, rozblikat chybovou LED nebo eskalovat na restart pomocí watchdogu. Stránka řízení smyčky popisuje celou plochu hooků smyčky.
8.6.5. KeyboardInterrupt¶
Když je skript zastaven zvenčí – obvykle tím, že jej IDE požádá o zastavení – požadavek dorazí dovnitř skriptu jako KeyboardInterrupt. Uvnitř asyncio.run() se propaguje stejně jako jakákoli jiná neošetřená výjimka: main je zrušena, každá úloha, kterou smyčka sleduje, je rovněž zrušena a KeyboardInterrupt se znovu vyvolá ven z asyncio.run(). Klauzule finally se při odchodu provedou, takže totéž čistící schéma ze stránky o rušení je to, co tuto situaci obslouží.