Come usare asyncio in Python

La funzionalità di programmazione asincrona di Python, o asincrona in breve, ti consente di scrivere programmi che fanno più lavoro non aspettando che le attività indipendenti finiscano. La asynciolibreria inclusa con Python ti offre gli strumenti per utilizzare l'asincronizzazione per l'elaborazione dell'I / O del disco o della rete senza far aspettare tutto il resto.

asyncio fornisce due tipi di API per gestire le operazioni asincrone: di  alto livello  e di  basso livello . Le API di alto livello sono le più generalmente utili e sono applicabili alla più ampia varietà di applicazioni. Le API di basso livello sono potenti, ma anche complesse e utilizzate meno frequentemente.

Ci concentreremo sulle API di alto livello in questo articolo. Nelle sezioni seguenti, esamineremo le API di alto livello più comunemente utilizzate  asyncioe mostreremo come possono essere utilizzate per operazioni comuni che coinvolgono attività asincrone. 

Se sei completamente nuovo in asincrono in Python, o potresti usare un aggiornamento su come funziona, leggi la mia introduzione a Python asincrono prima di tuffarti qui.

Esegui coroutine e attività in Python

Naturalmente, l'uso più comune di asyncioè eseguire le parti asincrone del tuo script Python. Ciò significa imparare a lavorare con coroutine e attività. 

I componenti asincroni di Python, comprese le coroutine e le attività, possono essere utilizzati solo con altri componenti asincroni e non con Python sincrono convenzionale, quindi è necessario  asyncio colmare il divario. Per fare ciò, usi la  asyncio.run funzione:

import asyncio

async def main ():

print ("Waiting 5 seconds.")

per _ nell'intervallo (5):

aspetta asyncio.sleep (1)

Stampa (".")

print ("Attesa terminata.")

asyncio.run (main ())

Questo funziona  main(), insieme a qualsiasi coroutine  main() , e attende il ritorno di un risultato.

Come regola generale, un programma Python dovrebbe avere una sola  .run() istruzione, proprio come un programma Python dovrebbe avere solo una  main() funzione. Async, se usato con noncuranza, può rendere difficile la lettura del flusso di controllo di un programma. Avere un unico punto di ingresso per il codice asincrono di un programma evita che le cose diventino complicate.

Le funzioni asincrone possono anche essere pianificate come  attività o oggetti che avvolgono le coroutine e aiutano a eseguirle.

async def my_task ():

fare qualcosa()

task = asyncio.create_task (my_task ())

my_task() viene quindi eseguito nel ciclo degli eventi, con i suoi risultati memorizzati in  task.

Se si dispone di una sola attività da cui si desidera ottenere risultati, è possibile utilizzare  asyncio.wait_for(task) per attendere il completamento dell'attività, quindi utilizzare  task.result() per recuperarne il risultato. Ma se avete in programma una serie di compiti da eseguire e si desidera attendere per  tutto  di loro per finire, l'uso  asyncio.wait([task1, task2]) per raccogliere i risultati. (Nota che puoi impostare un timeout per le operazioni se non vuoi che vengano eseguite oltre un certo periodo di tempo.)

Gestisci un ciclo di eventi asincrono in Python

Un altro uso comune di  asyncio è gestire il ciclo di eventi asincrono  . Il ciclo di eventi è un oggetto che esegue funzioni e callback asincrone; viene creato automaticamente quando usi  asyncio.run(). In genere si desidera utilizzare un solo ciclo di eventi asincrono per programma, ancora una volta per mantenere le cose gestibili.

Se stai scrivendo software più avanzato, come un server, avrai bisogno di un accesso di livello inferiore al loop degli eventi. A tal fine, puoi "sollevare il cofano" e lavorare direttamente con gli interni del loop di eventi. Ma per lavori semplici non sarà necessario.

Leggi e scrivi dati con flussi in Python

Gli scenari migliori per l'asincronia sono le operazioni di rete a esecuzione prolungata, in cui l'applicazione potrebbe bloccarsi in attesa che qualche altra risorsa restituisca un risultato. A tal fine,  asyncio offre flussi, che sono meccanismi di alto livello per l'esecuzione di I / O di rete. Ciò include agire come un server per le richieste di rete.

asyncio utilizza due classi,  StreamReader e  StreamWriter, per leggere e scrivere dalla rete ad alto livello. Se vuoi leggere dalla rete, dovresti usare  asyncio.open_connection() per aprire la connessione. Quella funzione restituisce una tupla di  StreamReader e  StreamWriter oggetti e useresti i   metodi .read() e  .write()su ciascuno per comunicare.

Per ricevere connessioni da host remoti, utilizzare  asyncio.start_server(). La asyncio.start_server()funzione accetta come argomento una funzione di callback  client_connected_cb, che viene chiamata ogni volta che riceve una richiesta. Quella funzione di callback accetta istanze di  StreamReader e StreamWriter come argomenti, quindi puoi gestire la logica di lettura / scrittura per il server. (Vedi qui per un esempio di un semplice server HTTP che utilizza la   libreria asyncio-driven  aiohttp.)

Sincronizza le attività in Python

Le attività asincrone tendono a essere eseguite in modo isolato, ma a volte vorrai che comunichino tra loro. asyncio fornisce code e molti altri meccanismi per la sincronizzazione tra le attività:

  • Code : le  asyncio code consentono alle funzioni asincrone di allineare gli oggetti Python da utilizzare da altre funzioni asincrone, ad esempio per distribuire i carichi di lavoro tra diversi tipi di funzioni in base ai loro comportamenti.
  • Primitive di sincronizzazione : blocchi, eventi, condizioni e semafori asynciofunzionano come le loro controparti Python convenzionali. 

Una cosa da tenere a mente su tutti questi metodi è che sono  non  thread-safe. Questo non è un problema per le attività asincrone in esecuzione nello stesso ciclo di eventi. Ma se stai cercando di condividere informazioni con attività in un ciclo di eventi, thread del sistema operativo o processo diverso, dovrai utilizzare il  threading modulo ei suoi oggetti per farlo.

Inoltre, se si desidera  avviare  coroutine oltre i limiti del thread, utilizzare la  asyncio.run_coroutine_threadsafe() funzione e passare il ciclo di eventi da utilizzare con esso come parametro.

Metti in pausa una coroutine in Python

Un altro uso comune di  asyncio, e poco discusso, è aspettare un periodo di tempo arbitrario all'interno di una coroutine. Non puoi usare  time.sleep() per questo o bloccherai l'intero programma. Utilizzare invece  asyncio.sleep(), che consente ad altre coroutine di continuare a funzionare.

Usa l'asincronia di livello inferiore in Python

Infine, se pensi che l'app che stai creando possa richiedere asyncioi componenti di livello inferiore, dai un'occhiata in giro prima di iniziare a scrivere codice: ci sono buone probabilità che qualcuno abbia già creato una libreria Python asincrona che fa quello che ti serve.

Ad esempio, se hai bisogno di query DNS asincrone, controlla la  aiodns libreria e per le sessioni SSH asincrone, c'è  asyncSSH. Cerca PyPI con la parola chiave "async" (più altre parole chiave relative alle attività) o controlla l'elenco di Awesome Asyncio per le idee.