8.2. Corutine și sarcini¶
Corutinele sunt unitatea de lucru din care este construit un program asyncio; sarcinile sunt modul în care o aplicație rulează mai multe corutine concurent.
8.2.1. Corutine¶
O corutină este o funcție declarată cu async def
import asyncio
async def heartbeat(interval_ms):
while True:
print("tick")
await asyncio.sleep_ms(interval_ms)
Corpul arată ca o funcție obișnuită, cu un ingredient suplimentar: await. Oriunde corutina trebuie să aștepte ceva – o pauză, o citire din rețea, un eveniment care este setat – ea face await pe o expresie care știe cum să suspende corutina până când lucrul pe care îl așteaptă este gata. La fiecare await, corutina cedează controlul înapoi către asyncio; asyncio o reia din același punct odată ce operația așteptată s-a încheiat.
Modulul asyncio oferă două funcții de pauză:
asyncio.sleep()– argument în secunde, acceptă un float.asyncio.sleep_ms()– argument în milisecunde, primește un int. O extensie MicroPython; de obicei alegerea potrivită pe cameră, deoarece reglajele de temporizare din firmware sunt exprimate în milisecunde.
Un simplu async def nu face nimic de unul singur. Apelarea heartbeat(500) nu execută corpul; returnează un obiect corutină pe care asyncio trebuie să-l planifice. Cel mai simplu mod de a planifica unul este asyncio.run()
asyncio.run(heartbeat(500))
asyncio.run() pornește bucla de evenimente, planifică corutina pe care a primit-o ca punct de intrare de nivel superior, conduce bucla până când acea corutină returnează, apoi închide bucla. Pentru o singură corutină, acesta este întregul program. Pentru mai multe corutine, aplicația recurge la sarcini.
8.2.2. Sarcini¶
O sarcină este wrapper-ul asyncio din jurul unei corutine care spune planifică aceasta concurent cu cea curentă și lasă-mă să continui. asyncio.create_task() creează una și returnează un obiect Task care reprezintă lucrarea planificată:
task = asyncio.create_task(heartbeat("fast", 100))
Corutina este acum în programul buclei; apelantul nu a așteptat-o. Obiectul Task returnat este mânerul pe care apelantul îl folosește ulterior pentru a interacționa cu acea lucrare în curs.
Odată ce aplicația are mânerul, poate face trei lucruri cu el:
Așteaptă finalizarea sarcinii. Un
Taskeste el însuși awaitable.result = await tasksuspendă corutina curentă până când corutina luitaskreturnează, apoi reia cu orice a returnat acea corutină (sau ridică din nou orice excepție a ridicat).Anulează sarcina.
task.cancel()planifică ridicareaasyncio.CancelledErrorîn interiorul corutinei sarcinii la următorul săuawait, oferindu-i șansa de a rula cod de curățare într-un blocfinally. Pagina despre timeout-uri și anulare acoperă detaliile.Identific-o mai târziu.
asyncio.current_task()returneazăTaskpentru corutina care rulează în prezent. Majoritatea scripturilor nu o apelează niciodată; apare în instrumentare și în gestionarele de excepții.
Scriptul nu trebuie să captureze mânerul de fiecare dată. Sarcinile de fundal de unică folosință pe care aplicația le pornește și le lasă să ruleze pot renunța la valoarea returnată – bucla tot le planifică:
import asyncio
async def heartbeat(name, interval_ms):
while True:
print(name)
await asyncio.sleep_ms(interval_ms)
async def main():
asyncio.create_task(heartbeat("fast", 100))
asyncio.create_task(heartbeat("slow", 500))
await asyncio.sleep(5)
asyncio.run(main())
Cele două apeluri create_task planifică ambele heartbeat-uri fără a aștepta vreunul dintre ele. Controlul revine imediat la main, care apoi face await pe o pauză de cinci secunde. În timp ce doarme, cele două sarcini heartbeat avansează; bucla parcurge oricare sarcină este gata să ruleze. După cinci secunde main returnează, bucla închide orice sarcini sunt încă active, iar asyncio.run() revine la apelant.
Capturează mânerul ori de câte ori aplicația are nevoie efectiv de una dintre cele trei operații de mai sus. În practică, asta înseamnă aproape întotdeauna, deoarece închiderea curată a unei aplicații înseamnă anularea sarcinilor de fundal pe care le-a generat – pagina despre anulare acoperă acest tipar.
8.2.3. Regula celor două linii¶
Programul asyncio minimal este cele două linii cu care se încheie exemplele de mai sus:
async def main():
...
asyncio.run(main())
Tot restul – sarcinile pe care le creează aplicația, primitivele cu care le coordonează, fluxurile pe care le deschide – se întâmplă în interiorul main (și în interiorul corutinelor pe care main le generează). Când un script depășește bucla clasică while True: csi0.snapshot() a camerei, răspunsul nu este să apelezi asyncio.run() în mai multe locuri; ci să integrezi noua lucrare în main ca mai multe sarcini.