8.6. Poikkeukset¶
Poikkeukset asyncio-skriptin sisällä käyttäytyvät lähes samalla tavalla kuin tavallisessa Pythonissa – ne etenevät ylöspäin kutsuketjussa, kunnes jokin ne nappaa. Lähes, koska tehtävät suoritetaan rinnakkain, joten polku ”ylöspäin” ei ole se polku, joka loi tehtävän. Tällä sivulla käsitellään, minne poikkeukset menevät kussakin yleisessä muodossa.
8.6.1. Yhden korutiinin sisällä¶
Korutiinin sisällä oleva try/except nappaa poikkeukset, joita mikä tahansa sen await-kutsu nostaa, tavalliseen tapaan:
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
Mikään tässä ei ole asynciolle ominaista – except-lause näkee fetch-funktion sisällä nostetut poikkeukset aivan kuin kyseessä olisi ollut tavallinen funktiokutsu.
8.6.2. Tehtävässä, jota sovellus odottaa¶
Kun Task-tehtävänä suoritettava korutiini nostaa poikkeuksen, poikkeus tallennetaan tehtävään. Seuraavan kerran, kun jokin awaitttaa kyseistä tehtävää, poikkeus nostetaan uudelleen await-kohdassa:
task = asyncio.create_task(may_fail())
try:
result = await task
except OSError:
log("may_fail failed")
Sama pätee asyncio.gather()-funktioon. Oletuskäyttäytyminen – yksi lapsi nostaa poikkeuksen, muut perutaan, ja poikkeus etenee ulos gather-kutsusta – juontuu tästä mekanismista.
8.6.3. Tehtävässä, jota kukaan ei odota¶
Tehtävä, jota kukaan ei koskaan awaitta, on tapaus, joka vaatii huomiota. Poikkeus tapahtuu silti; silmukka huomaa tehtävän päättyneen käsittelemättömään poikkeukseen; mutta ei ole await-kohtaa, jossa se pääsisi esiin. Oletuskäyttäytyminen on tulostaa jäljitys sys.stderr-virran kautta ja jatkaa suoritusta – mikä on hyvä valvomattomalle diagnostiikalle, mutta huono ratkaisu sovellukselle, joka halusi tietää asiasta.
Oikea korjaus on yleensä odottaa tehtävää. Joko suoraan, muistamalla kahva ja odottamalla sitä sammutuksen aikana, tai epäsuorasti gather()- tai wait_for()-funktion kautta. Aikakatkaisut ja peruminen-sivun ”sovelluksen sammuttaminen” -malli nappaa tämän tapauksen niille pitkäikäisille taustatehtäville, joita tyypillinen skripti synnyttää.
8.6.4. Mukautettu poikkeuskäsittelijä¶
Kun hiljainen jäljitä-ja-jatka ei riitä, silmukka tarjoaa koukun – Loop.set_exception_handler – jonka sovellus voi korvata tehdäkseen jotain muuta:
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)
context-argumentti on sanakirja, jossa on avaimet 'message', 'exception' ja 'future'. Poikkeus saattaa puuttua tietyissä varoitustyyppisissä tapahtumissa, minkä vuoksi esimerkki käyttää .get()-metodia.
Tyypillisiä käyttötarkoituksia ovat virheen kirjaaminen flash-muistiin, virhe-LEDin vilkuttaminen tai eskalointi vahtikoiran (watchdog) uudelleenkäynnistykseen. loop control -sivulla käsitellään silmukan koukkujen koko pinta-ala.
8.6.5. KeyboardInterrupt¶
Kun skripti pysäytetään ulkopuolelta – yleensä IDE:n pyytäessä sitä lopettamaan – pyyntö saapuu skriptin sisälle KeyboardInterrupt-poikkeuksena. asyncio.run()-funktion sisällä se etenee samalla tavalla kuin mikä tahansa muu käsittelemätön poikkeus: main perutaan, jokainen silmukan seuraama tehtävä perutaan myös, ja KeyboardInterrupt nostetaan uudelleen ulos asyncio.run()-funktiosta. finally-lauseet suoritetaan ulospäin mentäessä, joten sama siivousmalli kuin perumissivulla hoitaa tämän.