Programação Assíncrona em Kotlin: Guia Completo
Programação assíncrona é uma abordagem de desenvolvimento na qual o programa não bloqueia a execução enquanto espera a conclusão de operações longas (por exemplo, requisições de rede, leitura de arquivos ou trabalho com banco de dados). Em vez disso, ele continua executando outras tarefas, e o resultado é retornado posteriormente. No mundo Kotlin, a principal ferramenta para trabalho assíncrono são as corrotinas (coroutines) — threads leves que permitem escrever código assíncrono quase como síncrono.
Neste artigo, vamos analisar como funciona a programação assíncrona em Kotlin, desde os fundamentos até técnicas avançadas. Você aprenderá como usar corrotinas de forma eficiente, evitar bloqueios e escrever aplicações escaláveis.
1. Fundamentos das Corrotinas em Kotlin
Corrotinas são o conceito-chave da programação assíncrona em Kotlin. Elas permitem pausar a execução de uma função sem bloquear a thread. Para trabalhar com corrotinas, utiliza-se a biblioteca kotlinx.coroutines. Aqui está um exemplo mínimo:
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) // atraso não bloqueante println("Mundo!") } println("Olá,")}Neste código, runBlocking cria um contexto bloqueante para a corrotina (geralmente usado apenas no main ou em testes), e launch inicia uma nova corrotina. A função delay é uma função suspend que pausa a corrotina sem bloquear a thread.
Conceitos-chave:
- CoroutineScope — escopo de visibilidade das corrotinas, gerencia seu ciclo de vida.
- CoroutineContext — conjunto de configurações (dispatcher, manipulador de exceções).
- Função Suspend — função que pode ser pausada e retomada posteriormente.
2. Funções Suspend e Dispatchers
Funções Suspend são a base do código assíncrono. Elas podem chamar outras funções suspend (delay, withContext) e pausar sua execução. Exemplo:
suspend fun fetchUserData(): String { delay(2000L) // simulação de requisição de rede return "Dados do usuário"}
fun main() = runBlocking { val data = async { fetchUserData() } println("Carregando...") println(data.await()) // aguardando o resultado}Dispatchers determinam em qual thread a corrotina será executada:
Dispatchers.Default— para tarefas intensivas de CPU (padrão).Dispatchers.IO— para operações de entrada/saída (rede, arquivos).Dispatchers.Main— para a thread da UI (Android, Swing).Dispatchers.Unconfined— não vinculado a uma thread específica (raramente usado).
Exemplo com troca de dispatcher:
suspend fun loadData(): String = withContext(Dispatchers.IO) { // executando operação pesada em thread de fundo Thread.sleep(1000L) // simulação de operação bloqueante "Resultado"}Usar o dispatcher correto é criticamente importante para o desempenho. Não use Dispatchers.Main para operações longas — isso bloqueará a UI.
3. Trabalhando com async/await e Canais
Para execução paralela de múltiplas tarefas, use async e await. async retorna um objeto Deferred, que pode ser aguardado:
fun main() = runBlocking { val result1 = async(Dispatchers.IO) { fetchFromApi("url1") } val result2 = async(Dispatchers.IO) { fetchFromApi("url2") } println("Resultado 1: ${result1.await()}") println("Resultado 2: ${result2.await()}")}
suspend fun fetchFromApi(url: String): String { delay(1000L) return "Resposta de $url"