Async/Await è una coppia di parole chiave per scrivere codice asincrono in stile sincrono, disponibile in Swift da iOS 13 e in Kotlin con le coroutine. Secondo Apple Swift Documentation, 2026, Async/Await sostituisce le catene di callback e GCD, rendendo il codice asincrono lineare e leggibile. La parola chiave async marca una funzione come asincrona, e await sospende la sua esecuzione fino al ricevimento del risultato.
Punti Chiave
Async/Await è una costruzione del linguaggio che permette di scrivere codice asincrono in modo lineare quanto il codice sincrono. In Swift, è apparso in iOS 13 / macOS 10.15 insieme al framework Swift Concurrency. In Kotlin, async/await è disponibile attraverso la libreria delle coroutine (kotlinx.coroutines) a partire dalla versione 1.3.
Prima di async/await, il codice asincrono era costruito su callback, GCD o RxSwift. Le catene di callback annidate portavano all'inferno delle callback — un annidamento profondo che complica la lettura e il debug. Async/Await risolve questo problema permettendo alle funzioni asincrone di “sospendersi” nei punti await e riprendere dopo aver ricevuto il risultato.
Secondo Google (2025), usare async/await in Kotlin riduce il numero di righe di codice asincrono del 40–60% rispetto all'approccio con callback. In Swift, l'adozione di Swift Concurrency ha raggiunto il 68% tra le applicazioni pubblicate nel 2025. iOS 16 ha aggiunto il supporto di Swift Concurrency in UIKit, SwiftUI e Foundation, rendendo async/await lo strumento standard per tutti i nuovi progetti Apple.
La differenza principale tra async/await e i thread è la sospensione senza blocco. Quando una funzione incontra await, si sospende, liberando il thread corrente per altre attività. Dopo il completamento dell'operazione asincrona, la funzione riprende sullo stesso thread o su un thread diverso. Questo si chiama multitasking cooperativo.
Una funzione async viene compilata in una macchina a stati — un automa finito che gestisce gli stati di esecuzione. Al punto await, il compilatore salva il contesto (variabili locali, indirizzo di ritorno) e passa il controllo al runtime. Quando il risultato è pronto, il runtime ripristina il contesto e continua l'esecuzione.
In Swift, questa macchina a stati è implementata a livello del compilatore SIL (Swift Intermediate Language). In Kotlin, viene utilizzato il meccanismo Continuation Passing Style (CPS): ogni funzione suspend accetta un parametro Continuation nascosto attraverso il quale viene restituito il risultato. Entrambi gli approcci garantiscono una ripresa sicura senza perdite di memoria.
L'approccio con callback richiede il passaggio di una closure che viene chiamata dopo il completamento dell'operazione. Con tre richieste sequenziali, si ottengono tre livelli di annidamento con gestione degli errori a ogni livello. Async/Await trasforma quelle stesse tre richieste in tre righe sequenziali con un singolo blocco catch.
GCD (DispatchQueue) risolve l'inferno delle callback attraverso code seriali e DispatchGroup, ma rimane verboso. Per un compito semplice — caricare utente, poi amici, poi UI — sono necessarie code, gruppi e blocchi notify. Async/Await esegue lo stesso compito con tre righe in un'unica funzione.
| Aspetto | Callback | GCD | Async/Await |
|---|---|---|---|
| Annidamento | Profondo (piramide della perdizione) | Medio (gruppi+notify) | Lineare (piatto) |
| Gestione errori | Per callback | Manuale | Unificata (try/catch) |
| Annullamento | Manuale | Limitato | Task.cancel() |
| Thread safety | Manuale | Tramite coda seriale | Tramite MainActor |
Secondo Apple WWDC 2024, Swift Concurrency async/await è l'approccio raccomandato per il nuovo codice. GCD rimane per l'integrazione con librerie C e scenari specifici di basso livello di gestione dei thread.
Task è l'unità base del lavoro asincrono in Swift. Un Task viene creato nel contesto di un attore esistente o su un thread arbitrario. All'interno di un Task, è possibile chiamare funzioni async tramite await. TaskGroup permette di avviare più attività figlie in parallelo e raccogliere i loro risultati — un analogo potenziato di DispatchGroup.
Actor è un tipo di riferimento thread-safe che protegge il suo stato dalle race condition. Il compilatore garantisce che l'accesso alle proprietà dell'attore sia possibile solo tramite chiamate async o all'interno dell'isolamento dell'attore. Actor sostituisce la DispatchQueue seriale per proteggere lo stato mutabile condiviso senza blocchi manuali.
Il principio della Structured Concurrency afferma: ogni attività asincrona ha un genitore, e il genitore non completa finché tutti i suoi figli non sono completati. Se l'attività A avvia Task { await B() }, allora A aspetta B. Questo previene perdite di attività e garantisce un ciclo di vita prevedibile.
In Kotlin, async/await è implementato tramite coroutine — thread leggeri del linguaggio. Una funzione viene marcata con la parola chiave suspend (analoga a async di Swift). Per avviare, si usa launch (spara e dimentica) o async (con risultato). Await viene chiamato per ottenere il risultato da async.
Dispatcher determina su quale pool di thread viene eseguita la coroutine: Dispatchers.Main per l'interfaccia utente, Dispatchers.IO per rete/disco e Dispatchers.Default per attività intensive di CPU. Structured Concurrency è garantita tramite CoroutineScope — quando l'ambito viene annullato, tutte le coroutine figlie vengono annullate.
Secondo JetBrains (2025), il 97% delle app Android su Google Play usa kotlinx.coroutines, e l'82% di esse impiega async/await per le richieste di rete. Le coroutine sono diventate lo standard de facto per la programmazione asincrona in Android. Kotlin Multiplatform supporta anche le coroutine, permettendo l'uso di codice asincrono condiviso su Android, iOS e lato server.
Vediamo due esempi per ogni linguaggio: caricamento sequenziale dei dati e richieste parallele con TaskGroup o async/await.
await sospende l'esecuzione fino al ricevimento dei dati utente, poi fino al ricevimento degli amici. Nessuna closure annidata.
func loadProfile() async throws -> Profile {
let user = try await api.fetchUser()
let friends = try await api.fetchFriends(for: user.id)
return Profile(user: user, friends: friends)
}
TaskGroup avvia attività in parallelo e raccoglie i risultati in un array. L'ordine dei risultati potrebbe non corrispondere all'ordine di avvio.
func loadParallel() async throws -> [String] {
await withThrowingTaskGroup(of: String.self) { group in
group.addTask { try await api.fetchName() }
group.addTask { try await api.fetchEmail() }
group.addTask { try await api.fetchAvatar() }
var results = [String]()
for try await value in group { results.append(value) }
return results
}
}
Le funzioni suspend possono chiamare altre funzioni suspend. Analogo a Swift: chiamate sequenziali.
suspend fun loadProfile(): Profile {
val user = api.fetchUser()
val friends = api.fetchFriends(user.id)
return Profile(user, friends)
}
In Kotlin, il parallelismo si ottiene tramite coroutineScope con async per ogni richiesta.
suspend fun loadParallel(): List<String> = coroutineScope {
val name = async { api.fetchName() }
val email = async { api.fetchEmail() }
val avatar = async { api.fetchAvatar() }
listOf(name.await(), email.await(), avatar.await())
}
Task { } dimenticato — una funzione async non può essere chiamata da un contesto sincrono senza essere avvolta in un Task. In Swift, questo viene compilato con errore. In Kotlin, richiede l'avvio tramite lifecycleScope o viewModelScope. Tentare di chiamare una funzione suspend da una funzione normale non compilerà.
Non tutte le funzioni devono essere asincrone. Le attività intensive di CPU senza I/O non traggono vantaggio da async — è meglio eseguirle su DispatchQueue con QoS .userInitiated. Async/Await è ottimizzato per operazioni legate a I/O: rete, disco, attesa.
Usare .result sul thread principale (attesa sincrona del risultato di una funzione async) porta al blocco. In Swift, Task.synchronousWait non è raccomandato da Apple. In Kotlin, runBlocking sul thread principale è un antipattern — usa lifecycleScope.
Se un Task non viene salvato in una proprietà della classe, dopo aver lasciato il contesto viene annullato. In Swift, Task ha ambito — il suo ciclo di vita è legato al contesto creatore. Salva il Task in una proprietà se necessiti di un'operazione di lunga durata.
Domande Frequenti
Async/Await è una costruzione del linguaggio che non richiede creare code. DispatchQueue è un'API di sistema per gestire i thread. Async/await viene compilato in una macchina a stati a livello di linguaggio, GCD — in syscall a livello di sistema operativo.
No — await sospende la funzione ma libera il thread per altre attività. Dopo il completamento dell'operazione, la funzione riprende su qualsiasi thread disponibile dal pool.
Sì — tramite Task.cancel() in Swift o Job.cancel() in Kotlin. L'annullamento si propaga a tutte le attività figlie secondo Structured Concurrency. Il codice verifica il flag di annullamento tramite Task.isCancelled o ensureActive().
MainActor è un attore le cui proprietà e metodi vengono sempre eseguiti sul thread principale. Marcando una funzione con @MainActor, garantisci che gli aggiornamenti dell'interfaccia utente avvengano sul thread corretto, sostituendo DispatchQueue.main.async.
Async/Await è disponibile in Swift (iOS 13+), Kotlin (tramite kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, Rust e Go (tramite goroutine con zucchero sintattico).
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche