Programação Assíncrona em Kotlin: guia completo na prática
As aplicações modernas exigem alto desempenho e capacidade de resposta. A programação assíncrona em Kotlin com coroutines tornou-se o padrão de facto para trabalhar com requisições de rede, bancos de dados e operações de arquivos. Neste guia, abordaremos os conceitos principais, mostraremos exemplos práticos e forneceremos modelos prontos para uso em seus projetos.
1. Fundamentos das coroutines em Kotlin
Coroutines são threads leves que permitem pausar a execução de uma função sem bloquear a thread principal. Ao contrário das threads tradicionais, as coroutines podem ser pausadas a qualquer momento e retomadas posteriormente, tornando-as ideais para operações assíncronas.
1.1. Adicionando dependências
Para começar, adicione as dependências no build.gradle.kts:
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") // para Android}1.2. Primeira coroutine
Vamos criar uma coroutine simples usando runBlocking — uma ponte entre o mundo síncrono e assíncrono:
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) // atraso não bloqueante println("Mundo!") } println("Olá,") delay(2000L) // aguarda a conclusão da coroutine}Neste exemplo, launch inicia uma nova coroutine, e delay é uma função suspensa (suspend) que não bloqueia a thread.
2. Funções suspend e paralelismo estruturado
2.1. Criando funções suspend
Qualquer função que possa ser pausada deve ser marcada com o modificador suspend. Essas funções só podem ser chamadas de dentro de coroutines ou de outras funções suspend:
suspend fun fetchUserData(userId: String): User { delay(2000L) // simula uma requisição de rede return User(userId, "Ivan Petrov")}
suspend fun fetchUserPosts(userId: String): List<Post> { delay(1500L) return listOf(Post("Título 1"), Post("Título 2"))}2.2. Execução sequencial
Por padrão, as coroutines são executadas sequencialmente dentro de um mesmo launch:
fun main() = runBlocking { val time = measureTimeMillis { val user = fetchUserData("123") val posts = fetchUserPosts("123") println("${user.name} tem ${posts.size} posts") } println("Tempo de execução: $time ms") // ~3500 ms}2.3. Execução paralela com async/await
Para execução paralela, use async e await:
fun main() = runBlocking { val time = measureTimeMillis { val user = async { fetchUserData("123") } val posts = async { fetchUserPosts("123") } println("${user.await().name} tem ${posts.await().size} posts") } println("Tempo de execução: $time ms") // ~2000 ms (paralelo)}3. Dispensadores de coroutines e contextos
Os dispensadores determinam em qual thread ou pool de threads a coroutine será executada. Os principais dispensadores são:
- 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 de UI (Android, Swing)
- Dispatchers.Unconfined — não vinculado a uma thread específica
3.1. Exemplo com Dispatchers.IO
fun main() = runBlocking { launch(Dispatchers.IO) { // Requisição de rede val response = httpClient.get("https://api.example.com/data") withContext(Di