Python asincrono/attende spiegato: il tutorial del 2026
Async in Python ha la reputazione di creare confusione. Non deve essere così. Una volta interiorizzata una frase - async consente a UN thread di eseguire più attività legate a I/O interlacciate - tutto il resto segue.
Questo tutorial copre: cosa fanno effettivamente "async def" e "await", quando async vince rispetto a quando è lo strumento sbagliato, i quattro modelli utilizzati da ogni codebase asincrono (sequenziale, raccolta, semaforo, timeout) e i tre modi in cui i principianti ingoiano silenziosamente gli errori.
Il modello mentale di una frase
Async in Python è concorrenza cooperativa in un singolo thread. Quando il codice raggiunge "await", il controllo ritorna al ciclo di eventi, che può eseguire un'altra attività mentre la prima è in attesa di I/O. Una volta completato l'I/O, il ciclo riprende la prima attività da dove era stata sospesa.
Questo è tutto. Nessun thread, nessun processo, nessun parallelismo (nel senso di CPython con GIL). Solo un thread che si destreggia tra molte attività che trascorrono la maggior parte del tempo in attesa su rete/disco/database.
La sincronizzazione è più veloce quando il collo di bottiglia è in attesa, non in elaborazione.
Il programma asincrono minimo vitale di tre righe
async def hello()— dichiara una coroutine. La chiamata ahello()non esegue il codice; crea un oggetto coroutine.await asyncio.sleep(1)— restituisce il controllo al ciclo degli eventi per 1 secondo.asyncio.run(hello())— crea un ciclo di eventi ed esegue la coroutine fino al completamento.
Regola: `await` solo all'interno di `async def`. Chiamare "await" in una funzione regolare è un "SyntaxError".
Quando l'asincrono vince (e quando no)
L'asincrono vince: lavoro legato all'I/O
Qualsiasi attività che trascorre la maggior parte del suo tempo in ATTESA è un candidato per l'asincrono:
- Richieste HTTP (API esterne, scraping, webhook)
- Interrogazioni del database
- Il file legge/scrive
- Flussi WebSocket/gRPC
- Consumatori della coda (Redis, RabbitMQ, Kafka)
Esempio: recupero di 100 URL.
Accelerazione dell'orologio da parete di 50-100 volte per lavori pesanti di I/O. È qui che l'asincrono guadagna il suo budget di complessità.
Async PERDE: lavoro legato alla CPU
Async non fornisce parallelismo per il calcolo. Poiché è cooperativo a thread singolo, un hot loop della CPU blocca l'intero ciclo di eventi.
Tutti gli altri task muoiono durante l'esecuzione di pbkdf2. Per il lavoro legato alla CPU, utilizzare "multiprocessing" o "concurrent.futures.ProcessPoolExecutor", non async.
Carico di lavoro misto: run_in_executor
Se UN passaggio è pesante per la CPU e il resto è I/O, esegui il wrap del passaggio della CPU in modo che venga eseguito in un pool di thread senza bloccare il ciclo:
Il None = pool di thread predefinito. Ottimo per chiamare librerie di sincronizzazione di terze parti che sono lente ma non è possibile riscrivere.
I quattro modelli
1. Sequenziale: un'attività alla volta
Da utilizzare quando ciascuna richiesta dipende dal risultato di quella precedente.
2. asyncio.gather — attiva tutto in una volta
Scelta predefinita per attività I/O indipendenti. Ma: se raccogli 10.000 attività su un'API, otterrai descrittori di file con velocità limitata o esaurimento. È qui che entra in gioco lo schema del semaforo.
3. Semaforo: limite alla concorrenza
"asyncio.Semaphore(N)" consente al massimo N attività di superare "async with sem:" in qualsiasi momento. Le altre attività sono in coda. Utilizzare per qualsiasi API esterna con un limite di velocità o un database con un pool di connessioni.
4. Timeout: abbandona il lavoro lento
"asyncio.wait_for" annulla l'attività sottostante dopo N secondi. Essenziale per qualsiasi codice asincrono che comunica con una rete: non fare mai un'attesa illimitata.
Le tre trappole per l'inghiottimento degli errori
Trappola 1: asyncio.gather con return_exception=True
Utile quando vuoi conservare risultati parziali ma LETALE se non controlli tutti i risultati:
Senza il controllo gli errori svaniscono silenziosamente.
Trappola 2: compiti "spara e dimentica".
Se si solleva background_work(), Python emette un avviso allo spegnimento e va avanti. Per qualsiasi cosa ti interessi, memorizza l'attività e "aspettala" oppure utilizza "asyncio.TaskGroup" (3.11+) che propaga tutti gli errori.
Trappola 3: sincronizza il codice all'interno di async
"richieste" è sincrono. Anche all'interno di un async def, requests.get blocca l'intero ciclo finché non arriva la risposta. Utilizza httpx (o aiohttp) per HTTP asincrono.
Regola: non utilizzare mai una libreria I/O sincrona all'interno del codice asincrono. Se la libreria non ha una variante asincrona, racchiuderla in run_in_executor.
asyncio.TaskGroup: il modo del 2026 di distribuirsi a ventaglio
Python 3.11 ha aggiunto TaskGroup, che risolve il problema "fire-and-forget" in modo pulito:
Concorrenza strutturata: nessun errore perso, nessuna attività orfana. Preferisci questo al semplice create_task nel codice moderno.
Esempio reale: raschiatore a velocità limitata
Tutti e quattro i modelli in 15 righe: concorrenza limitata (semaforo), timeout (wait_for), rami di errore espliciti (nessuna immissione) e raccolta per fan-out. Copia e incolla questa forma in qualsiasi attività I/O asincrona.
Async in Python ha una vera curva di apprendimento, ma il modello mentale è semplice: un thread, pause cooperative in await, I/O si sovrappone. Padroneggia gather + Semaphore + wait_for + TaskGroup e avrai coperto il 95% del lavoro asincrono che scriverai.
Passaggio successivo: il percorso Python Senior comprende 20 lezioni pratiche su asyncio, TaskGroup, aiohttp/httpx e i modelli di concorrenza di cui sopra, con Python reale in esecuzione nel tuo browser.