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.