Async/Await: Perché Sette Runtime Non Riescono a Concordare sulla Semantica
Async/Await: La Divergenza Silenziosa
La maggior parte dei programmatori presume che quando scrivono codice async/await, stiano utilizzando un primitivo di concorrenza ben compreso e standardizzato. Dopotutto, la proposta è sempre la stessa: scrivere codice concorrente che assomiglia a codice lineare. Ma un nuovo articolo del Cognitive Engineering Lab della Brown University rivela una realtà sorprendente: nessuno dei due principali runtime async implementa la stessa semantica.
In "A Design Space Exploration of Async/Await," il ricercatore Gavin Gray e colleghi hanno analizzato sette implementazioni di rilievo—Asyncio e Trio di Python, Tokio e Smol di Rust, C#, JavaScript e Swift—e hanno scoperto che un semplice programma fire-and-forget produce quattro output diversi tra i runtime. Peggio ancora, quando hanno testato tre variazioni di quel programma, nessun runtime ha prodotto risultati identici in tutte le variazioni.
Cosa Dovrebbe Stampare Questo Programma?
Considera un esempio minimo: una funzione scrive due righe di log con un ritardo tra di esse, e un'altra funzione la genera come task in background senza attendere. La funzione principale attende il generatore, dorme brevemente, poi stampa una terza riga.
Intuitivamente, potresti aspettarti "ABC" o "AC" o "ACB"—ma la risposta effettiva dipende interamente dal tuo linguaggio. Swift stampa "AC" perché cancella il task in background quando la funzione generatrice esce. Trio di Python stampa "ABC" perché attende il completamento del task in background prima di proseguire. JavaScript potrebbe stampare "ACB" a seconda dei tempi. Non esiste una risposta "giusta" universale—solo scelte di progettazione.
Nove Dimensioni della Semantica Async
Il contributo principale dell'articolo è un quadro formale che identifica nove dimensioni di progettazione indipendenti che determinano il comportamento osservabile. Queste rientrano in tre fasi del ciclo di vita:
Inizio della Vita
- Eagerizzazione: Chiamare una funzione async ne avvia immediatamente l'esecuzione (eager) o restituisce una coroutine inerte che viene eseguita solo quando attesa (lazy)? Tokio di Rust è eager; Asyncio di Python è lazy.
- Sospensione: I punti
awaitgarantiscono la sospensione del task corrente o possono completarsi in modo sincrono? C# garantisce la sospensione; JavaScript no.
Fine della Vita
- Estensione: Un task può sopravvivere alla funzione che lo ha generato (indefinito) o è limitato al suo generatore (dinamico)? Swift e Trio usano estensione dinamica; la maggior parte degli altri permette durate indefinite.
- Forza del Riferimento: Per i task indefiniti, il runtime mantiene un riferimento forte (impedendo la garbage collection) o debole? Questo influisce sulle perdite di memoria e sulla durata dei task.
- Distruzione: Quando termina l'estensione di un task, viene atteso fino al completamento, cancellato o semplicemente lasciato terminare con il programma? Swift cancella; Trio attende.
- Propagazione: Cosa succede alle eccezioni nei task non attesi? Alcuni linguaggi le rilanciano nei task dipendenti (distruttivo); altri le ingoiano silenziosamente.
Cancellazione
- Consapevolezza: Un task può rispondere a richieste di cancellazione, o la cancellazione è implicita e inevitabile?
- Direzione: La cancellazione si propaga dall'alto verso il basso (dalla radice alle dipendenze) o dal basso verso l'alto (dalle dipendenze ai dipendenti)?
- Persistenza: Una volta cancellato, un task può ignorare la cancellazione e continuare normalmente (transitorio) o rimane cancellato anche se tenta di procedere?
Perché Swift Stampa "AC" e Trio Stampa "ABC"
Per vedere come queste dimensioni interagiscono, considera l'esempio dell'articolo. Sia Swift che Trio usano estensione dinamica—i task non possono sopravvivere alla loro funzione generatrice. Ma differiscono nella distruzione. Quando fire_and_forget restituisce, Swift cancella il task non atteso, quindi la scrittura del log non viene mai completata (da qui "AC"). Trio, invece, attende che il task finisca prima di permettere l'uscita dallo scope, producendo "ABC".
Questo non è un bug in nessuno dei due linguaggi—è una decisione di progettazione deliberata con compromessi. La cancellazione è più efficiente (nessun lavoro sprecato) ma rischia di perdere effetti collaterali importanti. L'attesa garantisce il completamento ma può bloccare il generatore.
Un Modello Formale per la Semantica Async
Oltre alla tassonomia, l'articolo contribuisce con una semantica formale su un calcolo fondamentale di programmi asincroni. Questo modello a macchina astratta permette ai ricercatori di tracciare esattamente perché diversi runtime divergono, con ogni decisione di progettazione rappresentata come un bivio nel percorso di riduzione.
Notevolmente, gli autori hanno validato il loro modello usando fuzzing differenziale—eseguendo gli stessi programmi sia sul modello che su tutti e sette i runtime reali per assicurarsi che le previsioni del modello corrispondano alla realtà. Questo approccio rigoroso significa che il quadro non è solo teorico; è empiricamente fondato.
Perché Questo è Importante per gli Sviluppatori
Queste scoperte hanno implicazioni pratiche immediate. Quando scegli un linguaggio per sistemi concorrenti, stai implicitamente scegliendo un insieme di garanzie semantiche—o la loro mancanza. Il quadro dell'articolo fornisce un vocabolario per confrontare queste scelte e capire perché un programma che funziona perfettamente in Rust potrebbe bloccarsi in Python.
Per gli autori di librerie, le implicazioni sono ancora più profonde. Una libreria che si comporta correttamente in un runtime async potrebbe comportarsi male silenziosamente in un altro, in particolare per quanto riguarda la cancellazione dei task e la propagazione delle eccezioni. Le dimensioni dell'articolo forniscono una checklist per testare la compatibilità tra runtime.
Mentre async/await continua a diffondersi in nuovi linguaggi—e mentre i linguaggi esistenti evolvono le loro implementazioni—questo spazio di progettazione diventerà solo più complesso. L'articolo della Brown University offre una mappa tanto necessaria di quel territorio, aiutando sviluppatori e progettisti di linguaggi a prendere decisioni informate su una delle caratteristiche più consequenziali della programmazione moderna.
Riferimento: Gray, G. et al. "A Design Space Exploration of Async/Await." Cognitive Engineering Lab, Brown University. 8 settembre 2026.
Related News

Litelm: LiteLLM senza il Gonfiore — Una Libreria di Routing LLM più Snella

Claude impone la politica di età 18+ con verifica Yoti

La dimostrazione di Navier-Stokes di OpenAI solleva dubbi sulla fiducia nella ricerca matematica

Shopify acquisisce Tailwind Labs: assicurare il futuro di Tailwind CSS

Meta Lancia Muse: Un Agente AI Personale e Sicuro per le Attività Quotidiane

