8.15. Zamke#
Isti obrasci koji čine asyncio ugodnim – bez preuzimanja kontrole, eksplicitni awaitovi – daju mu vlastiti skup oblika koji ujedaju. Ova stranica je katalog onih koji se javljaju dovoljno često da ih vrijedi poznavati.
8.15.1. Zaboravljanje await#
Pozivanje funkcije async def vraća objekt korutine. Ono ne pokreće tijelo funkcije. Da bi se zapravo izvela, korutina se mora awaitati ili omotati u zadatak:
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
Bug je tih – objekt korutine se stvara, odbacuje i nikada ne izvršava. Aplikacija nastavlja kao da je sve uspjelo. MicroPython će ponekad zabilježiti upozorenje da korutina nikada nije počekana; ponekad neće. Pregledajte ima li nedostajućih awaitova na svakom mjestu poziva koje izgleda kao poziv funkcije.
8.15.2. Tijesne petlje bez await#
Korutina koja se izvodi u petlji i nikada ne awaita monopolizira petlju događaja. Nijedan drugi zadatak ne napreduje dok petlja ne izađe ili ne prepusti kontrolu:
async def counter():
n = 0
while True:
n += 1 # bug: starves the loop
Rješenje je prepuštanje kontrole unutar petlje – obično await asyncio.sleep_ms(0) – tako da ostali spremni zadaci dobiju priliku za izvođenje. Računski zahtjevan posao također pripada unutar tog oblika: petlja za obradu slike koja se izvodi stotinama milisekundi po iteraciji trebala bi prepustiti kontrolu barem jednom po iteraciji kako se ostatak programa ne bi zaustavio.
8.15.3. Gutanje CancelledError#
Stranica o otkazivanju to je već detaljno pokrila. Ponavljamo to ovdje jer je to najčešći uzrok problema „moja se aplikacija ne želi ugasiti”: korutina hvata asyncio.CancelledError radi čišćenja i zaboravi je ponovno podići. Zadatak nastavlja s radom; pozivatelj koji je zatražio otkazivanje zauvijek visi čekajući da završi. Uvijek ponovno podignite iznimku nakon čišćenja ili koristite blok try/finally umjesto eksplicitnog except.
8.15.5. await na razini modula#
await je valjan samo unutar tijela async def. Pisanje na razini modula – izvan bilo koje korutine – je sintaksna pogreška:
# bug: not inside an async def
result = await fetch()
Rješenje je staviti posao u korutinu i pozvati je iz ulazne točke programa asyncio.run().
8.15.6. Višestruki pozivi asyncio.run#
MicroPython ima jednu petlju događaja. Pozivanje asyncio.run() dvaput zaredom – jednom za postavljanje, jednom za glavni posao – i dalje koristi istu petlju. Pozivanje iznutra korutine koja se izvodi je pogreška: petlja se već izvodi. Oba se slučaja najčešće javljaju kada skripta organski raste i autor je pokušava proširiti dodavanjem više poziva run() umjesto uklapanja novog posla u postojeći main.
8.15.7. Korištenje Event iz prekida#
asyncio.Event.set() je sigurno pozvati samo iznutra petlje događaja. Pozivanje iz rukovatelja GPIO prekida predstavlja opasnost od oštećenja. Za buđenje zadatka iz prekida koristite ThreadSafeFlag – stranica o njemu pokriva taj oblik.
8.15.8. Dugi sinkroni pozivi#
Korutina može počekati asyncio-vlastite primitive za čekanje; sve ostalo što pozove izvodi se sinkrono i blokira petlju dok se ne vrati. Blokirajući time.sleep() od 200 ms, zapis na SD karticu kojem treba 80 ms za pražnjenje, velika JPEG kompresija, poziv csi.CSI.snapshot() – svaki od njih drži petlju događaja punu svoju trajanju. Rješenje ovisi o pozivu:
Za
time.sleep: zamijenite ga sawait asyncio.sleepiliawait asyncio.sleep_ms.Za
csi.CSI.snapshot: koristite asinkroni omotač za snimanje koji gradi stranica o snimanju.Za dugotrajno računanje (obrada slike, JPEG kodiranje): prihvatite trošak ili razbijte posao na dijelove koji
awaitaju između iteracija.
Asyncio ne može učiniti sinkroni poziv neblokirajućim. Može samo dopustiti drugim korutinama da se izvode dok nešto drugo čeka.