8.15. Úskalí¶
Tytéž vzory, které dělají asyncio příjemným – žádné předbíhání, explicitní awaity – mu dávají vlastní sadu situací, které dokážou potrápit. Tato stránka je katalogem těch, které se objevují dostatečně často na to, aby se o nich vyplatilo vědět.
8.15.1. Zapomenutí na await¶
Volání funkce async def vrací objekt korutiny. Nespustí tělo funkce. Aby se skutečně vykonalo, musí se na korutinu awaitovat nebo ji zabalit do úlohy:
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
Chyba je tichá – objekt korutiny se vytvoří, zahodí a nikdy nevykoná. Aplikace pokračuje, jako by vše fungovalo. MicroPython někdy zaloguje varování, že na korutinu nikdy nebylo počkáno; někdy ne. Při každém místě volání, které vypadá jako volání funkce, prověřte chybějící awaity.
8.15.2. Těsné smyčky bez await¶
Korutina, která běží ve smyčce a nikdy awaituje, monopolizuje smyčku událostí. Žádná jiná úloha nepostoupí, dokud smyčka neskončí nebo nepředá řízení:
async def counter():
n = 0
while True:
n += 1 # bug: starves the loop
Řešením je yield uvnitř smyčky – obvykle await asyncio.sleep_ms(0) – aby ostatní připravené úlohy dostaly šanci běžet. Do tohoto tvaru patří i výpočetně náročná práce: smyčka zpracování obrazu, která běží stovky milisekund na iteraci, by měla alespoň jednou za iteraci předat řízení, aby se zbytek programu nezasekl.
8.15.3. Spolknutí CancelledError¶
Stránka o rušení se tomuto již podrobně věnovala. Opakuje se to zde, protože je to nejčastější příčina problému „moje aplikace se nevypne“: korutina zachytí asyncio.CancelledError za účelem úklidu a zapomene ji znovu vyvolat. Úloha běží dál; volající, který si o zrušení požádal, navždy visí a čeká na její dokončení. Po úklidu vždy znovu vyvolejte výjimku, nebo místo explicitního except použijte blok try/finally.
8.15.5. await na úrovni modulu¶
await je platné pouze uvnitř těla async def. Jeho zápis na úrovni modulu – mimo jakoukoli korutinu – je syntaktickou chybou:
# bug: not inside an async def
result = await fetch()
Řešením je vložit práci do korutiny a zavolat ji ze vstupního bodu programu asyncio.run().
8.15.6. Více volání asyncio.run¶
MicroPython má jednu smyčku událostí. Volání asyncio.run() dvakrát za sebou – jednou pro nastavení, jednou pro hlavní práci – stále používá stejnou smyčku. Volání zevnitř běžící korutiny je chyba: smyčka už běží. Oba případy se nejčastěji objeví, když skript organicky roste a autor se jej snaží rozšířit přidáváním dalších volání run(), namísto aby novou práci začlenil do existující main.
8.15.7. Použití Event z přerušení¶
asyncio.Event.set() je bezpečné volat pouze zevnitř smyčky událostí. Jeho volání z obslužné rutiny GPIO přerušení představuje riziko poškození. Pro probuzení úlohy z přerušení použijte místo toho ThreadSafeFlag – stránka o něm tento tvar pokrývá.
8.15.8. Dlouhá synchronní volání¶
Korutina může čekat na vlastní čekací primitiva asyncio; cokoli jiného, co volá, běží synchronně a blokuje smyčku, dokud se to nevrátí. Blokující time.sleep() na 200 ms, zápis na SD kartu, jehož vyprázdnění trvá 80 ms, velká JPEG komprese, volání csi.CSI.snapshot() – každé z nich drží smyčku událostí po celou svou dobu trvání. Řešení závisí na volání:
Pro
time.sleep: nahraďte jejawait asyncio.sleepneboawait asyncio.sleep_ms.Pro
csi.CSI.snapshot: použijte asynchronní obal snapshotu, který buduje stránka o snímání.Pro dlouhé výpočty (zpracování obrazu, JPEG kódování): přijměte tu cenu nebo rozdělte práci na části, které mezi iteracemi
awaitují.
Asyncio nedokáže učinit synchronní volání neblokujícím. Dokáže pouze nechat běžet jiné korutiny zatímco se čeká na něco jiného.