8.15. Sudenkuopat¶
Samat mallit, jotka tekevät asyncio:sta mukavan – ei esivaltaa, eksplisiittiset awaitt – antavat sille oman joukon hankalia muotoja. Tämä sivu on luettelo niistä, jotka tulevat vastaan riittävän usein, jotta niitä kannattaa tuntea.
8.15.1. await-lauseen unohtaminen¶
async def -funktion kutsuminen palauttaa korutiiniobjektin. Se ei suorita funktion runkoa. Sen tosiasialliseksi suorittamiseksi korutiinia on awaittava tai se on käärittävä tehtävään:
async def main():
send_request() # bug: returns the coroutine, does nothing
await send_request() # right: run it to completion
asyncio.create_task(send_request()) # right: run it concurrently
Vika on hiljainen – korutiiniobjekti luodaan, hylätään eikä koskaan suoriteta. Sovellus etenee kuin kaikki olisi toiminut. MicroPython kirjaa joskus varoituksen siitä, että korutiinia ei koskaan odotettu; joskus se ei tee niin. Tarkista puuttuvien awaitien varalta jokainen kutsupaikka, joka näyttää funktiokutsulta.
8.15.2. Tiiviit silmukat ilman await-lausetta¶
Korutiini, joka pyörii silmukassa eikä koskaan awaittaa, monopolisoi tapahtumasilmukan. Mikään muu tehtävä ei etene, ennen kuin silmukka poistuu tai luovuttaa vuoron:
async def counter():
n = 0
while True:
n += 1 # bug: starves the loop
Korjaus on yield silmukan sisällä – yleensä await asyncio.sleep_ms(0) – jotta muut valmiit tehtävät saavat mahdollisuuden suorittua. Laskentaintensiivinen työ kuuluu myös tähän muotoon: kuvankäsittelysilmukan, joka kestää satoja millisekunteja iteraatiota kohden, tulisi luovuttaa vuoro vähintään kerran iteraatiossa, jotta muu ohjelma ei pysähdy.
8.15.3. CancelledError-poikkeuksen nieleminen¶
peruutussivu käsitteli tämän jo yksityiskohtaisesti. Toistamme sen tässä, koska se on yleisin syy siihen, että ”sovellukseni ei sammu”: korutiini nappaa asyncio.CancelledError -poikkeuksen siivousta varten ja unohtaa nostaa sen uudelleen. Tehtävä jatkaa suoritustaan; kutsuja, joka pyysi peruutusta, jää ikuisesti odottamaan sen valmistumista. Nosta aina uudelleen siivouksen jälkeen tai käytä try/finally -lohkoa eksplisiittisen exceptin sijaan.
8.15.5. Moduulitason await¶
await on kelvollinen vain async def -rungon sisällä. Sen kirjoittaminen moduulitasolla – minkään korutiinin ulkopuolella – on syntaksivirhe:
# bug: not inside an async def
result = await fetch()
Korjaus on sijoittaa työ korutiiniin ja kutsua sitä ohjelman asyncio.run() -aloituspisteestä.
8.15.6. Useat asyncio.run -kutsut¶
MicroPythonissa on yksi tapahtumasilmukka. asyncio.run() -funktion kutsuminen kahdesti peräkkäin – kerran asennukseen, kerran päätyöhön – käyttää silti samaa silmukkaa. Sen kutsuminen käynnissä olevan korutiinin sisältä on virhe: silmukka on jo käynnissä. Molemmat tapaukset tulevat vastaan useimmiten silloin, kun skripti kasvaa orgaanisesti ja tekijä yrittää laajentaa sitä lisäämällä lisää run() -kutsuja sen sijaan, että taittaisi uuden työn olemassa olevaan mainiin.
8.15.7. Event -luokan käyttö keskeytyksestä¶
asyncio.Event.set() -metodia on turvallista kutsua vain tapahtumasilmukan sisältä. Sen kutsuminen GPIO-keskeytyskäsittelijästä on turmelusriski. Tehtävän herättämiseen keskeytyksestä käytä sen sijaan ThreadSafeFlag -luokkaa – sitä käsittelevä sivu esittelee muodon.
8.15.8. Pitkät synkroniset kutsut¶
Korutiini voi odottaa asyncio:n omia odotusprimitiivejä; kaikki muu, mitä se kutsuu, suoritetaan synkronisesti ja estää silmukan, kunnes se palaa. 200 ms:n estävä time.sleep(), SD-korttikirjoitus, jonka tyhjentäminen kestää 80 ms, suuri JPEG-pakkaus, csi.CSI.snapshot() -kutsu – jokainen näistä pitää tapahtumasilmukkaa koko kestonsa ajan. Korjaus riippuu kutsusta:
time.sleep-kutsulle: korvaa seawait asyncio.sleep- taiawait asyncio.sleep_ms-kutsulla.csi.CSI.snapshot-kutsulle: käytä asynkronista snapshot-käärettä, jonka kaappaussivu rakentaa.Pitkälle laskennalle (kuvankäsittely, JPEG-koodaus): hyväksy kustannus tai pilko työ paloihin, jotka
awaittavat iteraatioiden välillä.
Asyncio ei voi tehdä synkronisesta kutsusta estämätöntä. Se voi vain antaa muiden korutiinien suorittua sillä aikaa, kun jokin muu odottaa.