8.6. Undantag¶
Undantag inuti ett asyncio-skript beter sig nästan likadant som i vanlig Python – de propagerar uppåt genom anropskedjan tills något fångar dem. Nästan eftersom uppgifter körs parallellt, så vägen ”uppåt” är inte den väg som skapade uppgiften. Den här sidan täcker var undantag tar vägen i var och en av de vanliga formerna.
8.6.1. Inuti en korutin¶
Ett try/except inuti en korutin fångar undantag som kastas av allt den awaitar, på det vanliga sättet:
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
Inget asyncio-specifikt här – except-satsen ser undantag som kastas inuti fetch som om det hade varit ett vanligt funktionsanrop.
8.6.2. I en uppgift som applikationen väntar på¶
När en korutin som körs som en Task kastar ett undantag lagras undantaget på uppgiften. Nästa gång något awaitar den uppgiften kastas undantaget på nytt vid await
task = asyncio.create_task(may_fail())
try:
result = await task
except OSError:
log("may_fail failed")
Detsamma gäller asyncio.gather(). Standardbeteendet – ett barn kastar ett undantag, andra avbryts, undantaget propagerar ut ur gather – kommer från den här mekanismen.
8.6.3. I en uppgift som ingen väntar på¶
En uppgift som ingen någonsin awaitar är fallet som kräver uppmärksamhet. Undantaget inträffar fortfarande; loopen märker att uppgiften avslutades med ett ohanterat undantag; men det finns inget await där det kan komma fram. Standardbeteendet är att skriva ut ett stackspår via sys.stderr och fortsätta köra – vilket är fint för en obevakad diagnostik, men passar dåligt för en applikation som ville veta.
Den rätta lösningen är vanligtvis att vänta på uppgiften. Antingen direkt, genom att komma ihåg handtaget och vänta på det vid avstängning, eller implicit genom gather() eller wait_for(). Mönstret ”stänga ner en applikation” på sidan Timeouts och avbrytning fångar det här fallet för de långlivade bakgrundsuppgifter som ett typiskt skript startar.
8.6.4. Anpassad undantagshanterare¶
När tyst stackspår-och-fortsätt inte räcker exponerar loopen en hake – Loop.set_exception_handler – som applikationen kan åsidosätta för att göra något annat:
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)
Argumentet context är en dict med nycklarna 'message', 'exception' och 'future'. Undantaget kan saknas vid vissa varningsliknande händelser, vilket är varför exemplet använder .get().
Typiska användningar är att logga felet till flashminne, blinka med en fel-LED eller eskalera till en omstart via watchdog. Sidan loop-kontroll täcker hela ytan av loop-hakar.
8.6.5. KeyboardInterrupt¶
När ett skript stoppas utifrån – vanligtvis genom att IDE:n ber det att stanna – anländer begäran inuti skriptet som en KeyboardInterrupt. Inuti asyncio.run() propagerar det på samma sätt som vilket annat ohanterat undantag som helst skulle göra: main avbryts, varje uppgift som loopen spårar avbryts också, och KeyboardInterrupt kastas på nytt ut ur asyncio.run(). finally-satser körs på vägen ut, så samma städmönster från sidan om avbrytning är det som hanterar det.