8.15. Capcane¶
Aceleași tipare care fac asyncio plăcut – fără preempțiune, cu await-uri explicite – îi conferă propriul set de forme care mușcă. Această pagină este catalogul celor care apar suficient de des încât să merite să fie cunoscute.
8.15.1. Uitarea unui await¶
Apelarea unei funcții async def returnează un obiect corutină. Nu rulează corpul funcției. Pentru a o executa efectiv, corutina trebuie să fie așteptată cu await sau încapsulată într-o sarcină:
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
Eroarea este silențioasă – obiectul corutină este creat, abandonat și niciodată executat. Aplicația continuă ca și cum totul ar fi funcționat. MicroPython va jurnaliza uneori un avertisment că o corutină nu a fost niciodată așteptată; alteori nu o va face. Verifică lipsa await-urilor la fiecare loc de apel care arată ca un apel de funcție.
8.15.2. Bucle strânse fără await¶
O corutină care rulează într-o buclă și nu așteaptă niciodată cu await monopolizează bucla de evenimente. Nicio altă sarcină nu face progrese până când bucla nu iese sau nu cedează controlul:
async def counter():
n = 0
while True:
n += 1 # bug: starves the loop
Soluția este o cedare (yield) în interiorul buclei – de obicei await asyncio.sleep_ms(0) – astfel încât alte sarcini gata să ruleze să aibă o șansă. Munca intens computațională ține și ea de această formă: o buclă de procesare a imaginilor care rulează sute de milisecunde per iterație ar trebui să cedeze controlul cel puțin o dată per iterație, astfel încât restul programului să nu se blocheze.
8.15.3. Înghițirea CancelledError¶
Pagina despre anulare a acoperit deja acest lucru în detaliu. Îl repetăm aici deoarece este cea mai frecventă cauză a problemei „aplicația mea nu se închide”: o corutină prinde asyncio.CancelledError în scopuri de curățare și uită să o ridice din nou. Sarcina continuă să ruleze; apelantul care a cerut anularea așteaptă la nesfârșit ca ea să se termine. Ridică întotdeauna din nou excepția după curățare sau folosește un bloc try/finally în loc de un except explicit.
8.15.5. await la nivel de modul¶
await este valid doar în interiorul unui corp async def. Scrierea lui la nivel de modul – în afara oricărei corutine – este o eroare de sintaxă:
# bug: not inside an async def
result = await fetch()
Soluția este să pui munca într-o corutină și să o apelezi din punctul de intrare asyncio.run() al programului.
8.15.6. Apeluri multiple asyncio.run¶
MicroPython are o singură buclă de evenimente. Apelarea asyncio.run() de două ori la rând – o dată pentru configurare, o dată pentru munca principală – folosește în continuare aceeași buclă. Apelarea ei din interiorul unei corutine care rulează este o eroare: bucla rulează deja. Ambele cazuri apar cel mai des atunci când un script crește organic, iar autorul încearcă să-l extindă adăugând mai multe apeluri run() în loc să integreze noua muncă în main-ul existent.
8.15.7. Utilizarea Event dintr-o întrerupere¶
asyncio.Event.set() poate fi apelată în siguranță doar din interiorul buclei de evenimente. Apelarea ei dintr-un gestionar de întreruperi GPIO reprezintă un pericol de corupere. Pentru a trezi o sarcină dintr-o întrerupere, folosește în schimb ThreadSafeFlag – pagina despre ea acoperă forma.
8.15.8. Apeluri sincrone lungi¶
O corutină poate aștepta propriile primitive de așteptare ale asyncio; orice altceva apelează rulează sincron și blochează bucla până când returnează. Un time.sleep() blocant de 200 ms, o scriere pe cardul SD care durează 80 ms pentru a fi golită, o compresie JPEG mare, un apel csi.CSI.snapshot() – fiecare dintre acestea ține bucla de evenimente pe toată durata sa. Soluția depinde de apel:
Pentru
time.sleep: înlocuiește-l cuawait asyncio.sleepsauawait asyncio.sleep_ms.Pentru
csi.CSI.snapshot: folosește învelișul de instantaneu asincron construit de pagina de capturare.Pentru calcule lungi (procesare de imagini, codare JPEG): acceptă costul sau împarte munca în bucăți care așteaptă cu
awaitîntre iterații.
Asyncio nu poate face un apel sincron neblocant. El poate doar să lase alte corutine să ruleze în timp ce altceva este în așteptare.