Le coroutine (Coroutines) sono thread leggeri in Kotlin per la programmazione asincrona, disponibili attraverso la libreria kotlinx.coroutines. Secondo JetBrains Kotlin Documentation, 2026, Coroutines permettono di sospendere l'esecuzione di una funzione senza bloccare un thread, a differenza dei Thread tradizionali. Le coroutine vengono eseguite su un pool di thread limitato, rendendole mille volte più leggere dei thread nativi. Kotlin Coroutines sono completamente integrate con Android Jetpack, Retrofit, Room e altre librerie popolari dell'ecosistema Android.
Punti Chiave
Coroutines sono un meccanismo di programmazione asincrona in Kotlin, implementato nella libreria kotlinx.coroutines. A differenza dei thread del sistema operativo, le coroutine non sono legate a un thread specifico: possono sospendersi su un thread e riprendersi su un altro. Un singolo thread può eseguire migliaia di coroutine, passando dall'una all'altra con overhead minimo.
Le coroutine sono apparse in Kotlin 1.3 (2018) come funzionalità sperimentale e sono diventate stabili in Kotlin 1.5 (2021). Coroutines risolvono il problema del callback hell in modo simile a async/await, ma forniscono un'API più ricca: canali (Channel), Flow, gestione delle eccezioni nella gerarchia Job e integrazione diretta con Android Lifecycle.
Secondo JetBrains (2025), ogni coroutine consuma circa 100 byte di memoria contro 1+ MB per un thread nativo. Ciò consente di eseguire milioni di coroutine in una singola applicazione senza rischio di OutOfMemoryError. È proprio la leggerezza delle coroutine che le rende lo strumento preferito per l'asincronia in Android.
Ogni coroutine Kotlin viene compilata in una macchina a stati utilizzando Continuation Passing Style (CPS). Il compilatore aggiunge un parametro Continuation nascosto a ogni funzione suspend. Continuation contiene il punto di ripresa e tutte le variabili locali. Quando una coroutine si sospende, il runtime salva Continuation e, quando riprende, lo ripristina su qualsiasi thread disponibile dal pool di Dispatcher.
suspend è una parola chiave di Kotlin che marca una funzione come sospendibile. Tale funzione può essere chiamata solo da un'altra funzione suspend o da una coroutine. All'interno di una funzione suspend, puoi chiamare altre funzioni suspend in qualsiasi ordine e ogni punto di chiamata è un potenziale punto di sospensione.
La meccanica è semplice: quando una funzione suspend chiama un'altra funzione suspend, si sospende in quel punto, liberando il thread. Una volta completata la funzione chiamata, il runtime continua l'esecuzione dalla posizione salvata. Questo si chiama annullamento cooperativo (cooperative cancellation) — nessun thread viene bloccato.
Importante: una funzione suspend non è asincrona per impostazione predefinita. L'ordine di esecuzione rimane sequenziale se non vengono utilizzati launch o async. suspend consente semplicemente alla funzione di essere sospesa senza bloccare il thread e di far parte del contesto della coroutine. Continuation Passing Style è un modello di compilazione in cui ogni funzione suspend riceve un callback Continuation nascosto e il compilatore genera una macchina a stati per gestire sospensioni e riprese.
CoroutineScope è un contesto che definisce il ciclo di vita delle coroutine. Tutte le coroutine devono essere avviate all'interno di un ambito. Quando un ambito viene annullato (ad esempio, al termine di un'Activity), tutte le sue coroutine figlie vengono automaticamente annullate. Ciò impedisce perdite di attività in background. Android Jetpack fornisce ambiti pronti per ogni componente: viewModelScope per ViewModel e lifecycleScope per Activity e Fragment, che vengono automaticamente annullati quando il componente corrispondente viene distrutto.
La concorrenza strutturata (Structured Concurrency) è un principio che garantisce che una coroutine non venga completata fino a quando tutte le sue coroutine figlie non sono state completate. La gerarchia Job forma un albero: una coroutine radice crea un job padre, i figli creano job figli. L'annullamento di un job padre si propaga a tutti i figli. Structured Concurrency è una differenza fondamentale tra coroutine e thread.
| Scope | Dove viene usato | Annullamento |
|---|---|---|
| GlobalScope | Solo per attività daemon | Non viene annullato automaticamente |
| viewModelScope | Android ViewModel | Alla pulizia del ViewModel |
| lifecycleScope | Android Activity/Fragment | Alla distruzione del lifecycle |
| coroutineScope | All'interno di funzione suspend | All'annullamento del job padre |
Un Job normale annulla tutti i fratelli quando una coroutine figlia fallisce. SupervisorJob è un'eccezione: un fallimento in una coroutine figlia non influisce sulle altre. Questo è importante quando più attività indipendenti vengono eseguite in parallelo e una di esse può fallire senza bisogno di annullare le altre.
Dispatchers determinano su quali thread vengono eseguite le coroutine. Dispatchers.Main — il thread principale dell'interfaccia utente Android. Dispatchers.IO — un pool per operazioni bloccanti (rete, disco). Dispatchers.Default — per attività intensive di CPU. Dispatchers.Unconfined — inizia nel thread corrente ma non garantisce di rimanerci. Scegliere il Dispatcher corretto è fondamentale per le prestazioni: un'attività IO su Default bloccherà il pool di calcolo, mentre un'attività CPU su IO creerà thread non necessari.
withContext — una funzione per cambiare Dispatcher all'interno di una coroutine. Ad esempio, una funzione suspend che analizza JSON può passare a Dispatchers.Default per il calcolo e tornare a Dispatchers.Main per aggiornare l'interfaccia utente. withContext è il costruttore più utilizzato nello sviluppo Android.
launch — avvia una coroutine, restituisce un Job, non restituisce un risultato (fire-and-forget). async — avvia una coroutine, restituisce un Deferred da cui è possibile ottenere il risultato tramite await. runBlocking — blocca il thread corrente per eseguire una coroutine (solo per test e funzioni main). Scelta del costruttore dipende dallo scenario: launch è adatto per eventi e aggiornamenti, async per attività con risultato, runBlocking solo per test o punti di ingresso.
Consideriamo tre scenari pratici: una coroutine di base con launch, una chiamata parallela con async e la gestione degli errori con SupervisorJob.
viewModelScope.launch avvia una coroutine nel contesto di ViewModel. Quando il ViewModel viene pulito, la coroutine viene automaticamente annullata.
class ProfileViewModel : ViewModel() {
fun loadUser() {
viewModelScope.launch(Dispatchers.IO) {
val user = api.fetchUser()
withContext(Dispatchers.Main) {
showUser(user)
}
}
}
}
coroutineScope con async avvia tre richieste in parallelo. I risultati vengono raccolti tramite .await(). Se una richiesta fallisce, tutte vengono annullate.
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { api.fetchUser() }
val posts = async { api.fetchPosts() }
val stats = async { api.fetchStats() }
Dashboard(user.await(), posts.await(), stats.await())
}
SupervisorJob consente a ciascuna coroutine di completarsi in modo indipendente. Un errore in una richiesta non annulla le altre.
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch {
try { api.fetchUsers() } catch (e: Exception) { log(e) }
}
scope.launch {
try { api.fetchPosts() } catch (e: Exception) { log(e) }
}
I thread sono una primitiva del sistema operativo. Ogni thread ha il proprio stack (~1 MB) e richiede una chiamata di sistema per la creazione e il cambio. Le coroutine sono una primitiva del linguaggio, non legata al sistema operativo. Usano Continuation per salvare lo stato e passano a livello di runtime senza chiamate di sistema.
Secondo Google (2025), l'uso delle coroutine invece dei thread riduce il consumo di memoria per le attività in background nelle applicazioni Android del 90–95%. Tutte le moderne librerie Android (Retrofit, Room, WorkManager) hanno il supporto integrato per le coroutine tramite funzioni suspend. Ktor (framework client HTTP di JetBrains) è anch'esso interamente costruito sulle coroutine, fornendo funzioni suspend per ogni richiesta senza API di callback. Room supporta le coroutine tramite funzioni suspend nel DAO, consentendo di eseguire query al database senza bloccare il thread principale.
I thread rimangono necessari per il codice nativo tramite JNI, chiamate bloccanti CPU-intensive senza limite di tempo (rendering video, simulazioni) e durante l'integrazione con librerie C. Per tutto il resto — coroutine.
Domande Frequenti
Coroutine è un'unità di lavoro sospendibile che viene eseguita su un thread esistente. Un thread è una risorsa di sistema con il proprio stack. Le coroutine sono migliaia di volte più leggere dei thread e non bloccano le risorse quando vengono sospese.
Dispatchers.IO è progettato per operazioni I/O bloccanti (rete, file) e può creare nuovi thread quando necessario. Dispatchers.Default ha un pool di dimensioni fisse (numero di core CPU) per calcoli intensivi di CPU.
Job.cancel() annulla la coroutine e tutti i suoi figli. Per verificare l'annullamento all'interno di una coroutine, utilizzare ensureActive() — solleva una CancellationException se la coroutine è annullata.
Sì — tramite la libreria kotlinx-coroutines-rx3. Fornisce funzioni awaitSingle, awaitFirst e altre per convertire Observable/Single in funzioni suspend e viceversa tramite flowable.
Flow è un flusso di dati asincrono freddo, l'equivalente nelle coroutine di RxJava Observable. Flow emette valori in sequenza e si completa con un'eccezione o successo. Supporta map, filter, catch e altri operatori.
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