3.1 Modulo asyncio
Ormai nessuno crea più i propri thread per le sottotask asincrone. Anzi, si possono creare, ma queste azioni sono considerate troppo di basso livello e vengono utilizzate solo dagli sviluppatori di framework. E anche così, solo quando proprio non se ne può fare a meno.
Ora va di moda la programmazione asincrona, gli operatori async/await e le coroutine con i task. Ma andiamo per ordine...
Un po' di storia
Inizialmente in Python per risolvere i problemi di programmazione asincrona si utilizzavano le coroutine, basate sui generatori. Poi, in Python 3.4, è stato introdotto il modulo asyncio (a volte scritto come async IO), in cui sono implementati i meccanismi di programmazione asincrona. In Python 3.5 è stata introdotta la costruzione async/await.
E ora un po' di informazioni introduttive. Prima vi racconterò brevemente di tutte queste cose, poi nei dettagli, e infine ancora più nei dettagli. Non c'è altro modo, dato che quasi tutte lavorano in connessione, e spiegare dettagliatamente il funzionamento di un'entità senza riferirsi alle altre non è possibile.
Modulo asyncio
Il modulo asyncio è progettato per scrivere programmi asincroni, offrendo la possibilità di eseguire le task in parallelo. Supporta operazioni di input-output asincrone, timer, socket, esecuzioni di coroutine e multitasking, lavorando in uno o più cicli di eventi.
Coroutine (Coroutines)
Le coroutine sono funzioni asincrone, definite tramite la parola chiave async def. Le coroutine permettono di sospendere il proprio svolgimento tramite la parola chiave await, cosa che permette ad altre coroutine di eseguirsi in quel momento.
import asyncio
# Definizione della funzione asincrona (coroutine)
async def main():
print('Hello ...')
# Sospendiamo l'esecuzione per 1 secondo
await asyncio.sleep(1)
print('... World!')
# Avvio della funzione asincrona main() nel ciclo degli eventi
asyncio.run(main())
Ciclo degli eventi (Event Loop)
Il ciclo degli eventi gestisce l'esecuzione delle coroutine, task e altre operazioni asincrone. Chiamare asyncio.run() avvia il ciclo degli eventi e esegue la coroutine fino al completamento.
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# Otteniamo il ciclo degli eventi corrente
loop = asyncio.get_event_loop()
# Eseguiamo la coroutine fino al completamento
loop.run_until_complete(main())
# Chiudiamo il ciclo degli eventi dopo il completamento di tutte le task
loop.close()
Task (Tasks)
Le task permettono di eseguire le coroutine in parallelo. Si creano tramite asyncio.create_task() o asyncio.ensure_future().
import asyncio
# Definizione della coroutine che verrà eseguita con un ritardo
async def say_after(delay, what):
# Sospendiamo l'esecuzione per il tempo specificato
await asyncio.sleep(delay)
print(what)
# Coroutine principale
async def main():
# Creiamo le task per eseguire in parallelo le coroutine
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# Aspettiamo il completamento di entrambe le task
await task1
await task2
# Avvio della coroutine principale
asyncio.run(main())
Futures (Futures)
Gli oggetti Future rappresentano i risultati delle operazioni asincrone, che saranno disponibili in futuro. Ad esempio, vengono utilizzati per aspettare il completamento di una task asincrona.
import asyncio
# Definizione di una coroutine che simula un lungo compito
async def long_running_task():
print('Task started')
# Sospendiamo l'esecuzione per 3 secondi
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# Coroutine principale
async def main():
# Creiamo un future per aspettare il completamento della task
future = asyncio.ensure_future(long_running_task())
# Aspettiamo il completamento della task e otteniamo il risultato
result = await future
print(f'Task result: {result}')
# Avvio della coroutine principale
asyncio.run(main())
3.2 Funzione asincrona — async def
Una funzione asincrona si dichiara come una normale, solo che prima della parola chiave def bisogna scrivere la parola async.
async def NomeFunzione(parametri):
codice funzione
Una funzione asincrona si dichiara come una normale, si chiama come una normale, ma restituisce un risultato diverso. Se chiami una funzione asincrona, restituirà non il risultato, ma un oggetto speciale — coroutine.
Puoi anche verificarlo:
import asyncio
async def main():
print("Hello World")
# Chiamata della funzione asincrona, che restituisce una coroutine
result = main()
# Controlliamo il tipo del risultato
print(type(result)) # <class 'coroutine'>
Cosa succede? Quando marchi una funzione con la parola async, è come se aggiungessi un decoratore che fa qualcosa del genere:
def async_decorator(func):
# Creiamo un oggetto Task
task = Task()
# Gli passiamo la nostra funzione func, affinché la esegua
task.target = func
# Aggiungiamo l'oggetto task alla coda delle task — Event Loop
eventloop.add_task(task)
# Restituiamo l'oggetto task
return task
E il tuo codice diventa simile a:
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
Il senso di questa analogia è il seguente:
Quando chiami una funzione asincrona, si crea un oggetto speciale Task, che eseguirà la tua funzione, ma in futuro. Forse fra 0.0001 secondi, o forse fra 10.
Questo oggetto task ti viene restituito subito come risultato della chiamata alla tua funzione asincrona. Questo è la coroutine. È possibile che la tua funzione asincrona non abbia nemmeno iniziato a essere eseguita, ma l'oggetto task (coroutine) ce l'hai già tu.
A cosa ti serve questo task (coroutine)? Non puoi fare molto con lui, ma puoi fare 3 cose:
- Aspettare che la funzione asincrona si esegua.
- Aspettare che la funzione asincrona finisca di eseguire e ottenere il risultato dalla coroutine.
- Aspettare che si eseguano 10 (qualsiasi numero) di funzioni asincrone.
Come farlo, te lo dirò più avanti.
3.3 Operatore await
La maggior parte delle azioni con la coroutine inizia con "aspettare l'esecuzione di una funzione asincrona". Perciò per questa azione abbiamo un operatore speciale await.
Devi semplicemente scriverlo prima della coroutine:
await coroutine
O direttamente prima della chiamata della funzione asincrona:
await funzione_asincrona(argomenti)
Quando Python trova nel codice l'operatore await, sospende l'esecuzione della funzione corrente e aspetta che la coroutine si esegua — fino al termine della funzione asincrona a cui si riferisce la coroutine.
Importante! L'operatore await è usato solo all'interno di una funzione asincrona per sospendere l'esecuzione fino a quando un'altra coroutine o operazione asincrona non si è conclusa.
Questo è fatto per semplificare il processo di switch tra le chiamate delle funzioni asincrone. Tale chiamata await è essenzialmente una dichiarazione "qui aspetteremo non si sa quanto — occupatevi di eseguire altre funzioni asincrone".
Esempio:
import asyncio
# Definizione della funzione asincrona
async def async_print(text):
print(text)
# Funzione asincrona principale
async def main():
# Usiamo await per aspettare l'esecuzione della funzione asincrona
await async_print("Hello World")
# Avvio del ciclo principale degli eventi e esecuzione della coroutine main()
asyncio.run(main()) #esegue la funzione asincrona
In realtà l'operatore await lavora in modo ancora più ingegnoso — restituisce anche il risultato dell'esecuzione della funzione asincrona, su cui è stato chiamato.
Esempio:
import asyncio
# Definizione della funzione asincrona che somma due numeri
async def async_add(a, b):
return a + b
# Funzione asincrona principale
async def main():
# Usiamo await per ottenere il risultato dell'esecuzione di async_add
sum = await async_add(100, 200)
print(sum)
# Avvio del ciclo principale degli eventi e esecuzione della coroutine main()
asyncio.run(main()) #esegue la funzione asincrona
Quindi, per riassumere, l'operatore await:
- Sospende la funzione asincrona corrente fino a quando un'altra coroutine o operazione asincrona si è conclusa.
- Restituisce il risultato dell'esecuzione dell'operazione asincrona o della coroutine.
- Può essere utilizzato solo all'interno di una funzione asincrona.
GO TO FULL VERSION