Dispatchers — concetti chiave, tipi di dispatcher e come funzionano

Autore: IT Sectr Pubblicato: 2026-06-22 Tempo di lettura: 9 min

Dispatchers in Kotlin Coroutines sono componenti di CoroutineContext che determinano i thread per l'esecuzione delle coroutine: Main (thread UI), IO (rete e disco), Default (compiti intensivi di CPU) e Unconfined (thread corrente). Ogni dispatcher gestisce un pool di thread specializzato ottimizzato per un tipo specifico di lavoro. Secondo la guida JetBrains, 2024, scegliere il dispatcher giusto è fondamentale per le prestazioni e la stabilità dell'applicazione.

Punti Chiave

  • Dispatchers — dispatcher di coroutine predefiniti che gestiscono la distribuzione dei compiti tra i thread
  • Dispatchers.Main — thread UI Android, per aggiornare l'interfaccia e lavorare con View
  • Dispatchers.IO — pool per richieste di rete, lettura/scrittura file e DB (fino a 64 thread)
  • Dispatchers.Default — pool per compiti intensivi di CPU basato sul numero di core del processore
  • Dispatchers.Unconfined — eredita il thread corrente, senza commutazione

Cosa sono i Dispatchers?

Dispatchers sono implementazioni dell'interfaccia CoroutineDispatcher, che sono elementi di CoroutineContext. Determinano su quale thread o pool di thread verrà eseguita la coroutine. Quando si crea una coroutine tramite launch o async, il dispatcher può essere passato come primo parametro: launch(Dispatchers.IO) { ... }. Se non viene specificato alcun dispatcher, viene ereditato dal CoroutineScope esterno.

Kotlin fornisce quattro dispatcher incorporati: Main, IO, Default, Unconfined. Ogni dispatcher utilizza il proprio pool di thread ottimizzato per un tipo specifico di operazione. Scegliere il dispatcher giusto determina le prestazioni dell'applicazione: una scelta errata porta a rallentamenti dell'interfaccia, core CPU inattivi o uso inefficiente dei thread.

DispatcherPool di threadMax threadUtilizzo
Dispatchers.MainUno (UI)1Aggiornamenti UI, LiveData, View
Dispatchers.IOPool IO64 (limitedParallelism)Rete, file, DB
Dispatchers.DefaultPool CPUN coreOrdinamento, parsing, calcoli
Dispatchers.UnconfinedThread correnteN/DOperazioni intermedie, test

Dispatchers.Main: Thread UI

Dispatchers.Main è il dispatcher che esegue le coroutine sul thread principale di Android. È progettato per operazioni relative all'interfaccia: aggiornare TextView, chiamare notifyDataSetChanged, lavorare con LiveData e StateFlow. In Android, questo dispatcher è implementato tramite Handler (Looper.getMainLooper()).

kotlin
// Correct switch to Main for UI updates
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchData()
    withContext(Dispatchers.Main) {
        _uiState.value = data
    }
}

Se una coroutine è già sul dispatcher Main, un withContext(Dispatchers.Main) aggiuntivo non crea overhead — il dispatcher controlla il thread corrente e salta la commutazione. withContext è il modo preferito per passare da un dispatcher all'altro.

Dispatchers.IO: Operazioni di Rete e Disco

Dispatchers.IO è un dispatcher ottimizzato per operazioni di I/O: richieste HTTP (Ktor, OkHttp), lettura e scrittura di file, lavoro con Room o SQLDelight. Utilizza un pool di 64 thread per impostazione predefinita, scalabile sotto carico. Ogni nuova richiesta I/O può creare un thread aggiuntivo fino al raggiungimento del limite.

Limitazione del Parallelismo

Per controllare il numero di operazioni I/O simultanee, utilizzare limitedParallelism(). Questa funzione crea un nuovo dispatcher con un limite sul numero di thread paralleli, impedendo l'esaurimento del pool durante le operazioni di massa.

kotlin
val limitedIo = Dispatchers.IO.limitedParallelism(4)

// Load 100 files with limit of 4 concurrent operations
coroutineScope {
    val files = (1..100).map { index ->
        async(limitedIo) {
            downloadFile("file_$index")
        }
    }
    files.awaitAll()
}

Utilizzare il dispatcher IO per tutte le operazioni in cui la coroutine trascorre il tempo in attesa (I/O-bound). I compiti intensivi di CPU sul dispatcher IO sono inefficienti — occupano thread destinati all'I/O, riducendo il throughput del sistema.

Dispatchers.Default: Compiti Intensivi di CPU

Dispatchers.Default è il dispatcher per le operazioni di calcolo che caricano il processore: ordinamento, filtraggio, parsing JSON (Moshi, Kotlinx Serialization), elaborazione immagini, calcoli. La dimensione del pool è pari al numero di core del processore (ma non meno di 2). Ciò garantisce il massimo utilizzo della CPU senza cambio di contesto.

kotlin
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
    return withContext(Dispatchers.Default) {
        input
            .parallelStream()
            .map { transform(it) }
            .toList()
    }
}

Non utilizzare Dispatchers.Default per operazioni I/O — questo bloccherà i thread del pool CPU che potrebbero elaborare compiti computazionali. La separazione di IO e Default consente un utilizzo ottimale delle risorse di sistema: i thread IO attendono I/O, i thread CPU sono costantemente occupati con calcoli.

Dispatchers.Unconfined: Thread Corrente

Dispatchers.Unconfined è un dispatcher speciale che non lega una coroutine a nessun pool. La coroutine inizia l'esecuzione nel thread in cui è stato chiamato launch/async, e dopo la sospensione riprende nel thread che ha chiamato resume. Questo comportamento è adatto per operazioni intermedie che non richiedono un contesto fisso.

kotlin
fun main() = runBlocking {
    launch(Dispatchers.Unconfined) {
        println("Before delay: ${Thread.currentThread().getName()}")
        delay(500L)
        println("After delay: ${Thread.currentThread().getName()}")
    }
}

Nel codice di produzione, Dispatchers.Unconfined è usato raramente. Casi d'uso principali: trasformazioni leggere prima di passare i dati a un altro dispatcher e test. Per carichi di produzione, utilizzare dispatcher espliciti — Unconfined è imprevedibile perché il thread di esecuzione dipende dall'implementazione di resume.

Come Scegliere un Dispatcher

La selezione del dispatcher dipende dal tipo di attività: operazioni UI → Main, I/O-bound → IO, CPU-bound → Default, intermedie → ereditare dallo scope. Per Android, si consiglia di avviare una coroutine sul dispatcher in cui viene eseguito il lavoro principale e passare a Main tramite withContext prima di aggiornare l'UI.

  • Non eseguire operazioni I/O sul dispatcher Main — questo blocca l'UI
  • Non eseguire compiti intensivi di CPU sul dispatcher IO — questo consuma inefficientemente i thread del pool IO
  • Utilizzare limitedParallelism per controllare la concorrenza durante operazioni I/O di massa
  • Cambiare dispatcher usando withContext, non creando un nuovo scope
  • Ereditare il dispatcher dallo scope se la coroutine non richiede un pool specifico

Per scenari complessi, combinare i dispatcher con l'operatore +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Questo crea un CoroutineContext con un dispatcher specificato, gestione degli errori e una gerarchia Job isolata.

Domande Frequenti

Qual è la differenza tra Dispatchers.IO e Dispatchers.Default?

Dispatchers.IO utilizza un pool fino a 64 thread per operazioni I/O-bound (attesa I/O), mentre Dispatchers.Default utilizza un pool basato sul numero di core CPU per compiti computazionali. Quando i thread scarseggiano, entrambi i pool possono condividere thread tra loro.

Posso creare il mio dispatcher in Kotlin Coroutines?

Sì, utilizzare newSingleThreadContext() per un singolo thread o newFixedThreadPoolContext() per un pool fisso. Per la produzione, utilizzare limitedParallelism() basato sui dispatcher esistenti — è più efficiente che creare nuovi pool.

Cosa succede quando Dispatchers.Main viene chiamato da un thread in background?

Se Dispatchers.Main non è disponibile (ad esempio, in un test JUnit o servizio in background), viene lanciata una IllegalStateException. Utilizzare TestCoroutineDispatcher per i test e Dispatchers.IO o Default per i servizi in background.

Come limitare il numero di operazioni I/O parallele?

Utilizzare Dispatchers.IO.limitedParallelism(N), dove N è il numero massimo di thread paralleli. Questo previene l'esaurimento del pool durante richieste di massa e fornisce parallelismo controllato.

Quando utilizzare Dispatchers.Unconfined?

Dispatchers.Unconfined è adatto per operazioni intermedie: trasformazioni leggere dei dati prima di passarli a un altro dispatcher, scenari di test. Nel codice Android di produzione, non è raccomandato a causa del thread di esecuzione indefinito dopo la sospensione.

Riepilogo

  • Dispatchers — componenti di CoroutineContext che determinano i thread di esecuzione delle coroutine
  • Dispatchers.Main — per operazioni UI Android, un thread principale
  • Dispatchers.IO — per operazioni di rete e disco, pool fino a 64 thread
  • Dispatchers.Default — per calcoli intensivi di CPU, pool basato sul numero di core
  • Dispatchers.Unconfined — senza legame al thread, per operazioni intermedie
  • withContext — il meccanismo principale per cambiare dispatcher all'interno di una coroutine
  • Scegliere il dispatcher sbagliato porta a rallentamenti dell'UI o uso inefficiente delle risorse

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