8.15. Valkuilen¶
Dezelfde patronen die asyncio prettig maken – geen preemptie, expliciete awaits – geven het ook een eigen verzameling valstrikken. Deze pagina is de catalogus van diegene die vaak genoeg voorkomen om de moeite waard te zijn om te kennen.
8.15.1. Vergeten te awaiten¶
Het aanroepen van een async def-functie geeft een coroutine-object terug. Het draait de body van de functie niet. Om die daadwerkelijk uit te voeren, moet de coroutine geawaitd worden of in een taak verpakt worden:
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
De bug is stil – het coroutine-object wordt aangemaakt, weggegooid en nooit uitgevoerd. De applicatie gaat verder alsof alles werkte. MicroPython logt soms een waarschuwing dat een coroutine nooit geawait is; soms doet het dat niet. Controleer op ontbrekende awaits bij elke aanroepplek die op een functieaanroep lijkt.
8.15.2. Strakke lussen zonder await¶
Een coroutine die in een lus draait en nooit awaitt, monopoliseert de event loop. Geen enkele andere taak boekt vooruitgang totdat de lus eindigt of de controle teruggeeft:
async def counter():
n = 0
while True:
n += 1 # bug: starves the loop
De oplossing is een yield binnen de lus – doorgaans await asyncio.sleep_ms(0) – zodat andere gereedstaande taken een kans krijgen om te draaien. Rekenintensief werk hoort ook in die vorm thuis: een beeldverwerkingslus die honderden milliseconden per iteratie draait, zou ten minste eenmaal per iteratie de controle moeten teruggeven, zodat de rest van het programma niet stilvalt.
8.15.3. Het inslikken van CancelledError¶
De pagina annulering behandelde dit al uitvoerig. We herhalen het hier omdat het de meest voorkomende oorzaak is van “mijn applicatie wil niet afsluiten”: een coroutine vangt asyncio.CancelledError op voor opruimdoeleinden en vergeet deze opnieuw op te werpen. De taak blijft draaien; de aanroeper die om de annulering vroeg, blijft voor eeuwig hangen in afwachting dat deze klaar is. Werp de uitzondering altijd opnieuw op na het opruimen, of gebruik een try/finally-blok in plaats van een expliciete except.
8.15.5. await op moduleniveau¶
await is alleen geldig binnen een async def-body. Het op moduleniveau schrijven – buiten elke coroutine – is een syntaxisfout:
# bug: not inside an async def
result = await fetch()
De oplossing is om het werk in een coroutine te plaatsen en deze aan te roepen vanuit het asyncio.run()-startpunt van het programma.
8.15.6. Meerdere asyncio.run-aanroepen¶
MicroPython heeft één event loop. Het twee keer achter elkaar aanroepen van asyncio.run() – eenmaal voor de opzet, eenmaal voor het hoofdwerk – gebruikt nog steeds dezelfde loop. Het van binnenuit een draaiende coroutine aanroepen is een fout: de loop draait al. Beide gevallen komen het vaakst voor wanneer een script organisch groeit en de auteur het probeert uit te breiden door meer run()-aanroepen toe te voegen in plaats van het nieuwe werk in de bestaande main op te nemen.
8.15.7. Gebruik van Event vanuit een interrupt¶
asyncio.Event.set() is alleen veilig aan te roepen van binnen de event loop. Het aanroepen vanuit een GPIO-interrupthandler is een corruptierisico. Om een taak vanuit een interrupt te wekken, gebruik je in plaats daarvan ThreadSafeFlag – de pagina erover behandelt de vorm.
8.15.8. Lange synchrone aanroepen¶
Een coroutine kan asyncio’s eigen wachtprimitieven awaiten; al het andere dat deze aanroept draait synchroon en blokkeert de loop totdat het terugkeert. Een blokkerende time.sleep() van 200 ms, een SD-kaartschrijfactie die 80 ms duurt om weg te schrijven, een grote JPEG-compressie, een aanroep van csi.CSI.snapshot() – elk daarvan houdt de event loop voor de volledige duur vast. De oplossing hangt af van de aanroep:
Voor
time.sleep: vervang het doorawait asyncio.sleepofawait asyncio.sleep_ms.Voor
csi.CSI.snapshot: gebruik de async snapshot-wrapper die de opnamepagina bouwt.Voor langdurige berekeningen (beeldverwerking, JPEG-codering): accepteer de kosten of breek het werk op in stukken die tussen iteraties door
awaiten.
Asyncio kan een synchrone aanroep niet niet-blokkerend maken. Het kan alleen andere coroutines laten draaien terwijl iets anders aan het awaiten is.