2.32. Coroutine¶
Una coroutine è una funzione che può sospendersi a metà esecuzione e riprendere in seguito dal punto in cui si era interrotta, con tutte le sue variabili locali intatte. Rispecchia il pattern dei generatori – un corpo di funzione che può essere sospeso e ripreso – con una differenza riguardo a chi guida ogni ripresa.
La sintassi di Python per scrivere una coroutine è la coppia di parole chiave async / await. async contrassegna la funzione come coroutine; await segna i punti al suo interno in cui è consentita la sospensione.
2.32.1. Definire una coroutine¶
Una funzione definita con async def è una funzione coroutine. Chiamarla non esegue il corpo; restituisce un oggetto coroutine che non è ancora stato avviato:
async def greet(name):
print("hello,", name)
coro = greet("Alice") # body NOT run yet
L’oggetto coroutine è sospeso all’inizio della funzione. Qualcosa deve guidarlo per far eseguire il corpo – quel qualcosa è un event loop, un componente runtime. MicroPython ne fornisce uno nel modulo asyncio. Per ora, considera la coroutine come «pronta per l’esecuzione, in attesa di un driver».
2.32.2. Sospensione all’interno di una coroutine¶
All’interno di una coroutine, un’espressione await sospende l’esecuzione finché il valore atteso non è pronto:
async def fetch_and_log():
data = await read_sensor()
print("got:", data)
Quando il corpo raggiunge await read_sensor(), la coroutine restituisce il controllo a ciò che la sta eseguendo. Quando read_sensor() termina, il driver riprende la coroutine alla riga successiva, con il risultato associato a data.
await è valido solo all’interno di una coroutine. Usarlo in una funzione normale è un errore di sintassi.
2.32.3. Relazione con i generatori¶
Coroutine e generatori condividono lo stesso meccanismo di base. La differenza sta in chi richiede ogni ripresa:
Un generatore produce valori; il consumatore richiede il successivo con
next()o iterando.Una coroutine cede il controllo; un event loop pianifica la ripresa quando l’operazione attesa è pronta.
Se l’handshake yield dei generatori ha senso, l’handshake delle coroutine è la stessa idea – solo guidato da un event loop anziché da un ciclo for.
Un event loop è un piccolo dispatcher che mantiene un elenco di coroutine in attesa di qualcosa (un timer, un evento di rete, il completamento di un’altra coroutine). A ogni iterazione sceglie una coroutine la cui attesa è stata soddisfatta, la riprende fino al successivo await, quindi registra ciò che quella coroutine sta ora attendendo e passa a un’altra coroutine pronta. Il risultato è che molti task avanzano concorrentemente su un singolo thread – ogni coroutine cede volontariamente il controllo nei suoi punti await, e il loop riempie quei momenti con qualunque altra coroutine sia pronta ad avanzare.
Internamente, await e yield usano la stessa funzionalità del runtime di Python per sospendere e riprendere una funzione. Le parole chiave differiscono perché differisce la convenzione che le circonda: yield restituisce un valore a un consumatore che lo richiede con next(); await cede il controllo a un event loop che pianifica la ripresa quando l’operazione attesa è pronta. async / await è essenzialmente una sintassi più recente per il pattern delle coroutine – le librerie più vecchie costruivano le coroutine direttamente sopra il meccanismo dei generatori, usando yield from (introdotto in Iteratori e generatori) per delegare la sospensione tra coroutine.
2.32.4. Le coroutine necessitano di un driver¶
Una coroutine è inerte senza un runtime che la guidi. Definirne una va bene; eseguirne una richiede un event loop. Il modulo asyncio di MicroPython fornisce quell’event loop. La sezione Asyncio illustra come avviare il loop, pianificare coroutine su di esso, condividere lo stato tra esse con lock ed eventi, gestire cancellazione e timeout, e strutturare un’applicazione reale attorno alle parole chiave async / await qui introdotte.