8.6. Excepties¶
Excepties binnen een asyncio-script gedragen zich vrijwel hetzelfde als in gewoon Python – ze propageren omhoog door de aanroepketen totdat iets ze opvangt. Vrijwel omdat taken parallel draaien, dus het pad “omhoog” is niet het pad dat de taak heeft aangemaakt. Deze pagina behandelt waar excepties naartoe gaan in elk van de gangbare vormen.
8.6.1. Binnen één coroutine¶
Een try/except binnen een coroutine vangt excepties op die worden opgeworpen door alles wat het awaitt, op de gebruikelijke manier:
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
Niets asyncio-specifieks hier – de except-clausule ziet excepties die binnen fetch worden opgeworpen alsof het een gewone functieaanroep was geweest.
8.6.2. In een taak waar de applicatie op wacht¶
Wanneer een coroutine die als een Task draait een exceptie opwerpt, wordt de exceptie op de taak opgeslagen. De volgende keer dat iets die taak awaitt, wordt de exceptie opnieuw opgeworpen bij de await
task = asyncio.create_task(may_fail())
try:
result = await task
except OSError:
log("may_fail failed")
Hetzelfde geldt voor asyncio.gather(). Het standaardgedrag – één kind werpt een exceptie op, andere worden geannuleerd, de exceptie propageert uit de gather – komt voort uit dit mechanisme.
8.6.3. In een taak waar niemand op wacht¶
Een taak waar nooit iemand op awaitt is het geval dat aandacht behoeft. De exceptie treedt nog steeds op; de loop merkt op dat de taak is geëindigd met een onafgehandelde exceptie; maar er is geen await waar deze kan opduiken. Het standaardgedrag is om een traceback af te drukken via sys.stderr en door te blijven draaien – wat prima is voor een onbeheerde diagnose, maar slecht past bij een applicatie die het wilde weten.
De juiste oplossing is meestal om op de taak te wachten. Ofwel direct, door de handle te onthouden en er tijdens het afsluiten op te wachten, of impliciet via gather() of wait_for(). Het “een applicatie afsluiten”-patroon van de pagina Timeouts en annulering vangt dit geval op voor de langlevende achtergrondtaken die een typisch script aanmaakt.
8.6.4. Aangepaste exceptie-handler¶
Wanneer stille traceback-en-doorgaan niet genoeg is, biedt de loop een hook – Loop.set_exception_handler – die de applicatie kan overschrijven om iets anders te doen:
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)
Het context-argument is een dict met de sleutels 'message', 'exception' en 'future'. De exceptie kan ontbreken bij bepaalde waarschuwingsachtige events, en daarom gebruikt het voorbeeld .get().
Typische toepassingen zijn het loggen van de fout naar flashgeheugen, het laten knipperen van een fout-LED, of het escaleren naar een watchdog-reboot. De pagina loop control behandelt het volledige oppervlak van loop-hooks.
8.6.5. KeyboardInterrupt¶
Wanneer een script van buitenaf wordt gestopt – meestal doordat de IDE vraagt het te stoppen – komt het verzoek binnen het script aan als een KeyboardInterrupt. Binnen asyncio.run() propageert het zoals elke andere onafgehandelde exceptie zou doen: main wordt geannuleerd, elke taak die de loop volgt wordt ook geannuleerd, en de KeyboardInterrupt wordt opnieuw opgeworpen uit asyncio.run(). finally-clausules draaien op de weg naar buiten, dus hetzelfde opschoonpatroon van de annuleringspagina handelt dit af.