Coroutines — concetti chiave, Job e Dispatchers in Kotlin

Autore: IT Sectr Pubblicato: 2026-03-16 Tempo di lettura: 8 min

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 — thread leggeri Kotlin per codice asincrono senza blocco
  • Funzione suspend — una funzione che può sospendersi e riprendersi senza bloccare un thread
  • Dispatcher determina il pool di thread per l'esecuzione della coroutine
  • Job — un descrittore di coroutine con supporto all'annullamento e tracciamento dello stato
  • CoroutineScope gestisce il ciclo di vita delle coroutine e il loro annullamento al completamento

Cosa sono le coroutine Kotlin

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.

Come funzionano le coroutine internamente

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.

Funzioni suspend: sospensione e ripresa

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.

  • Sospensione — la coroutine libera il thread senza bloccarlo
  • Ripresa — la coroutine continua da dove si è sospesa
  • Thread — una coroutine può sospendersi sul thread A e riprendersi sul thread B
  • Eccezioni — gestite tramite try/catch come nel codice sincrono

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 e concorrenza strutturata

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.

ScopeDove viene usatoAnnullamento
GlobalScopeSolo per attività daemonNon viene annullato automaticamente
viewModelScopeAndroid ViewModelAlla pulizia del ViewModel
lifecycleScopeAndroid Activity/FragmentAlla distruzione del lifecycle
coroutineScopeAll'interno di funzione suspendAll'annullamento del job padre

SupervisorJob per la gestione degli errori

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 e costruttori di coroutine

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.

Tre costruttori principali di coroutine

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.

Esempi di codice con coroutine in Kotlin

Consideriamo tre scenari pratici: una coroutine di base con launch, una chiamata parallela con async e la gestione degli errori con SupervisorJob.

Avviare una coroutine con launch

viewModelScope.launch avvia una coroutine nel contesto di ViewModel. Quando il ViewModel viene pulito, la coroutine viene automaticamente annullata.

kotlin
class ProfileViewModel : ViewModel() {
    fun loadUser() {
        viewModelScope.launch(Dispatchers.IO) {
            val user = api.fetchUser()
            withContext(Dispatchers.Main) {
                showUser(user)
            }
        }
    }
}

Richieste parallele con async

coroutineScope con async avvia tre richieste in parallelo. I risultati vengono raccolti tramite .await(). Se una richiesta fallisce, tutte vengono annullate.

kotlin
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())
}

Gestione degli errori con SupervisorJob

SupervisorJob consente a ciascuna coroutine di completarsi in modo indipendente. Un errore in una richiesta non annulla le altre.

kotlin
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) }
}

Coroutine vs thread: confronto e scenari

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.

  • Memoria — thread ~1 MB, coroutine ~100 byte. Un rapporto di 10.000 volte
  • Creazione — thread ~1 µs di syscall, coroutine ~0,01 µs a livello JVM
  • Cambio — thread ~0,1 µs (syscall), coroutine ~0,001 µs (continuation)
  • Massimo — migliaia di thread vs milioni di coroutine per dispositivo
  • Annullamento — un thread non può essere annullato esternamente (Thread.stop deprecato), una coroutine può tramite Job.cancel()

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.

Quando usare i thread invece delle coroutine

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

In cosa differisce una coroutine da un thread?

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.

Cos'è Dispatchers.IO e in cosa differisce da Default?

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.

Come annullare una coroutine in esecuzione?

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.

Le coroutine possono essere usate con RxJava?

Sì — tramite la libreria kotlinx-coroutines-rx3. Fornisce funzioni awaitSingle, awaitFirst e altre per convertire Observable/Single in funzioni suspend e viceversa tramite flowable.

Cos'è Flow nelle coroutine?

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

  • Coroutines — thread leggeri Kotlin con sospensione non bloccante tramite Continuation Passing Style
  • suspend — la parola chiave per marcare funzioni sospendibili
  • Dispatchers gestiscono il pool di thread: Main, IO, Default rispettivamente
  • CoroutineScope lega il ciclo di vita delle coroutine a un componente (Activity, ViewModel)
  • launch avvia una coroutine senza risultato, async/await — con risultato
  • Structured Concurrency garantisce l'annullamento gerarchico delle coroutine figlie
  • Coroutine vs thread — le coroutine sono 10.000 volte più leggere e sono lo standard per Android

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.

Discuti il progetto

Leggi anche