Dispatchers em Kotlin Coroutines são componentes de CoroutineContext que determinam as threads para execução de corrotinas: Main (thread da UI), IO (rede e disco), Default (tarefas intensivas de CPU) e Unconfined (thread atual). Cada dispatcher gerencia um pool de threads especializado otimizado para um tipo específico de trabalho. De acordo com o guia da JetBrains, 2024, escolher o dispatcher certo é crítico para o desempenho e a estabilidade da aplicação.
Pontos Principais
Dispatchers são implementações da interface CoroutineDispatcher, que são elementos de CoroutineContext. Eles determinam em qual thread ou pool de threads a corrotina será executada. Ao criar uma corrotina via launch ou async, o dispatcher pode ser passado como primeiro parâmetro: launch(Dispatchers.IO) { ... }. Se nenhum dispatcher for especificado, ele é herdado do CoroutineScope externo.
Kotlin fornece quatro dispatchers integrados: Main, IO, Default, Unconfined. Cada dispatcher usa seu próprio pool de threads otimizado para um tipo específico de operação. Escolher o dispatcher certo determina o desempenho da aplicação: uma escolha errada leva a lentidão na UI, núcleos de CPU ociosos ou uso ineficiente de threads.
| Dispatcher | Pool de Threads | Máx. de Threads | Uso |
|---|---|---|---|
| Dispatchers.Main | Um (UI) | 1 | Atualizações de UI, LiveData, View |
| Dispatchers.IO | Pool IO | 64 (limitedParallelism) | Rede, arquivos, BD |
| Dispatchers.Default | Pool CPU | N núcleos | Ordenação, análise, cálculos |
| Dispatchers.Unconfined | Thread atual | N/A | Operações intermediárias, testes |
Dispatchers.Main é o dispatcher que executa corrotinas na thread principal do Android. Ele é projetado para operações relacionadas à interface: atualizar TextView, chamar notifyDataSetChanged, trabalhar com LiveData e StateFlow. No Android, este dispatcher é implementado via Handler (Looper.getMainLooper()).
// Correct switch to Main for UI updates
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchData()
withContext(Dispatchers.Main) {
_uiState.value = data
}
}
Se uma corrotina já está no dispatcher Main, um withContext(Dispatchers.Main) adicional não cria sobrecarga — o dispatcher verifica a thread atual e pula a alternância. withContext é a forma preferida de alternar entre dispatchers.
Dispatchers.IO é um dispatcher otimizado para operações de E/S: requisições HTTP (Ktor, OkHttp), leitura e escrita de arquivos, trabalho com Room ou SQLDelight. Ele usa um pool de 64 threads por padrão, escalável sob carga. Cada nova requisição de E/S pode criar uma thread adicional até atingir o limite.
Para controlar o número de operações de E/S simultâneas, use limitedParallelism(). Esta função cria um novo dispatcher com um limite no número de threads paralelas, evitando o esgotamento do pool durante operações em massa.
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()
}
Use o dispatcher IO para todas as operações onde a corrotina passa tempo esperando (I/O-bound). Tarefas intensivas de CPU no dispatcher IO são ineficientes — elas ocupam threads destinadas a E/S, reduzindo a vazão do sistema.
Dispatchers.Default é o dispatcher para operações computacionais que carregam o processador: ordenação, filtragem, análise JSON (Moshi, Kotlinx Serialization), processamento de imagens, cálculos. O tamanho do pool é igual ao número de núcleos do processador (mas não menos que 2). Isso garante a máxima utilização da CPU sem alternância de contexto.
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
return withContext(Dispatchers.Default) {
input
.parallelStream()
.map { transform(it) }
.toList()
}
}
Não use Dispatchers.Default para operações de E/S — isso bloqueará threads do pool de CPU que poderiam estar processando tarefas computacionais. A separação de IO e Default permite a utilização ideal dos recursos do sistema: threads IO aguardam E/S, threads CPU estão constantemente ocupadas com cálculos.
Dispatchers.Unconfined é um dispatcher especial que não vincula uma corrotina a nenhum pool. A corrotina começa a execução na thread onde launch/async foi chamado, e após a suspensão retoma na thread que chamou resume. Este comportamento é adequado para operações intermediárias que não requerem um contexto fixo.
fun main() = runBlocking {
launch(Dispatchers.Unconfined) {
println("Before delay: ${Thread.currentThread().getName()}")
delay(500L)
println("After delay: ${Thread.currentThread().getName()}")
}
}
Em código de produção, Dispatchers.Unconfined é raramente usado. Principais casos de uso: transformações leves antes de passar dados para outro dispatcher e testes. Para cargas de produção, use dispatchers explícitos — Unconfined é imprevisível porque a thread de execução depende da implementação do resume.
A seleção do dispatcher depende do tipo de tarefa: operações de UI → Main, I/O-bound → IO, CPU-bound → Default, intermediárias → herdar do escopo. Para Android, recomenda-se lançar uma corrotina no dispatcher onde o trabalho principal é realizado e alternar para Main via withContext antes de atualizar a UI.
Para cenários complexos, combine dispatchers usando o operador +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Isso cria um CoroutineContext com um dispatcher especificado, tratamento de erros e uma hierarquia de Job isolada.
Perguntas Frequentes
Dispatchers.IO usa um pool de até 64 threads para operações I/O-bound (espera de E/S), enquanto Dispatchers.Default usa um pool baseado no número de núcleos de CPU para tarefas computacionais. Quando as threads são escassas, ambos os pools podem compartilhar threads entre si.
Sim, use newSingleThreadContext() para uma thread única ou newFixedThreadPoolContext() para um pool fixo. Para produção, use limitedParallelism() baseado em dispatchers existentes — isso é mais eficiente do que criar novos pools.
Se Dispatchers.Main não estiver disponível (por exemplo, em um teste JUnit ou serviço em segundo plano), uma IllegalStateException é lançada. Use TestCoroutineDispatcher para testes e Dispatchers.IO ou Default para serviços em segundo plano.
Use Dispatchers.IO.limitedParallelism(N), onde N é o número máximo de threads paralelas. Isso evita o esgotamento do pool durante requisições em massa e fornece paralelismo controlado.
Dispatchers.Unconfined é adequado para operações intermediárias: transformações leves de dados antes de passar para outro dispatcher, cenários de teste. Em código de produção Android, não é recomendado devido à thread de execução indefinida após a suspensão.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também