Kotlin: Coroutines do Kotlin Explicadas

Última atualização: 2026-08-26

Coroutines são o núcleo da programação assíncrona do Kotlin — a função suspend permite que Charlie escreva operações assíncronas com sintaxe síncrona, enquanto a concorrência estruturada garante nenhuma coroutine vazada.

1. O que Você Aprenderá


2. A História Real de um Arquiteto

(1) Ponto de Dor: Inferno de Callbacks e Explosão de Threads

A versão Java do OrderProcessor de Charlie usava CompletableFuture para lógica assíncrona — 3 níveis de callbacks aninhados já eram ilegíveis. A equipe usava 200 threads para requisições concorrentes, com sobrecarga de troca de contexto de CPU consumindo 40% da capacidade.

(2) A Solução com Coroutines

KOTLIN
// Java: callbacks aninhados de CompletableFuture
CompletableFuture<Order> future = fetchOrder(id)
    .thenCompose(order -> fetchCustomer(order.getCustomerId()))
    .thenApply(customer -> enrichOrder(order, customer))
    .exceptionally(ex -> handleError(ex));

// Kotlin: código com aparência sequencial, execução assíncrona
suspend fun processOrder(id: String): Order {
    val order = fetchOrder(id)           // Suspende, não bloqueia
    val customer = fetchCustomer(order.customerId)  // Suspende novamente
    return enrichOrder(order, customer)
}

Funções suspend fazem código assíncrono ler como síncrono — coroutines suspendem em vez de bloquear threads ao esperar. Uma thread pode lidar com 100.000 coroutines.


3. Coroutines vs Threads

(1) Diferenças Principais

KOTLIN
// Thread: 1 thread por tarefa concorrente (pesada)
// 100.000 threads = OOM (cada thread ~1MB de stack)

// Coroutine: threads virtuais leves
// 100.000 coroutines = tranquilo (cada coroutine ~poucas centenas de bytes)

fun main() = runBlocking {
    repeat(100_000) {
        launch {  // 100K coroutines - sem problema!
            delay(1_000)  // Suspende (não bloqueia)
            println("Coroutine $it concluída")
        }
    }
}

(2) Comparação Coroutine vs Thread

Dimensão Thread Coroutine
Custo de criação ~1MB de memória stack ~poucas centenas de bytes
Troca de contexto Nível de kernel do SO (lenta) Modo usuário (rápida)
Quantidade máxima Milhares Centenas de milhares
Bloqueio Bloqueia toda a thread Apenas suspende a coroutine
Cancelamento Inseguro (stop deprecado) Cancelamento cooperativo (seguro)
Exceções Difícil de propagar Propagação estruturada

(3) Diagrama de Princípio da Coroutine

100%
sequenceDiagram
    participant T as Thread
    participant C1 as Coroutine 1
    participant C2 as Coroutine 2
    participant IO as IO Operation

    T->>C1: Resume
    C1->>IO: fetchOrder(id)
    Note over C1: SUSPEND - thread está LIVRE
    T->>C2: Resume (mesma thread!)
    C2->>IO: fetchCustomer(id)
    Note over C2: SUSPEND - thread está LIVRE
    IO-->>C1: Resultado do Order
    Note over C1: RESUME
    C1-->>T: Continuar processamento
    IO-->>C2: Resultado do Customer
    Note over C2: RESUME

4. Funções suspend

(1) Conceitos Básicos

KOTLIN
// palavra-chave suspend: esta função pode suspender a coroutine
suspend fun fetchOrder(id: String): Order {
    delay(500)  // Simular chamada de rede (não-bloqueante)
    return Order(id, 299.99, "CONFIRMED")
}

// funções suspend só podem ser chamadas de coroutines ou outras funções suspend
suspend fun processOrder(id: String): Order {
    val order = fetchOrder(id)      // Ponto de suspensão
    val customer = fetchCustomer(order.customerId)  // Ponto de suspensão
    return order.copy(customer = customer)
}

(2) Regras de Funções suspend

Regra Descrição
Apenas chamável de coroutines ou funções suspend Imposto pelo compilador
Não bloqueia threads Suspende a coroutine, libera a thread
Pode chamar funções regulares Funções regulares não podem chamar suspend
Essencialmente CPS transformado O compilador converte suspend em uma máquina de estados

5. Os Três Pilares da Concorrência Estruturada

(1) CoroutineScope

KOTLIN
// CoroutineScope: define o tempo de vida das coroutines
// Todas as coroutines lançadas em um escopo estão ligadas ao seu tempo de vida

// runBlocking: bloqueia a thread atual até todas as coroutines completarem
runBlocking {
    launch { delay(1_000); println("Concluído") }
}

// coroutineScope: suspende (não bloqueia) até todos os filhos completarem
suspend fun fetchAll() = coroutineScope {
    val order = async { fetchOrder("ORD-001") }
    val customer = async { fetchCustomer("CUST-001") }
    Pair(order.await(), customer.await())
}

// Escopo personalizado (ex.: em uma classe)
class OrderService {
    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

    fun process(order: Order) {
        scope.launch { /* trabalho assíncrono */ }
    }

    fun shutdown() {
        scope.cancel()  // Cancelar todas as coroutines filhas
    }
}

(2) Job — Ciclo de Vida da Coroutine

KOTLIN
val job = launch {
    println("Trabalhando...")
    delay(1_000)
    println("Concluído")
}

// Estados do Job: New -> Active -> Completing -> Completed
//                                    -> Cancelling -> Cancelled
job.cancel()           // Solicitar cancelamento
job.join()             // Aguardar conclusão
job.cancelAndJoin()    // Cancelar + aguardar

(3) Dispatcher — Agendador de Threads

KOTLIN
// Dispatchers.Default: trabalho intensivo de CPU (paralelismo = núcleos de CPU)
launch(Dispatchers.Default) { computeOrderTax() }

// Dispatchers.IO: operações de I/O bloqueantes (até 64 threads)
launch(Dispatchers.IO) { fetchDataFromDb() }

// Dispatchers.Main: thread de UI (Android/Swing)
launch(Dispatchers.Main) { updateUI() }

// Dispatcher personalizado
val orderDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher()
launch(orderDispatcher) { processOrder() }

(4) Comparação de Dispatchers

Dispatcher Quantidade de Threads Caso de Uso Operações Típicas
Default Núcleos de CPU Intensivo de CPU Ordenação, computação
IO Até 64 I/O bloqueante Rede, banco de dados
Main 1 Atualizações de UI Android/Desktop
Personalizado Personalizado Necessidades específicas Pool de threads isolado

6. launch vs async

(1) launch — Dispara-e-Esquece

KOTLIN
// launch: dispara-e-esquece (retorna Job, não resultado)
val job: Job = launch {
    delay(1_000)
    println("Trabalho em background concluído")
}
job.join()  // Aguardar conclusão

(2) async — Aguarda Resultado

KOTLIN
// async: retorna Deferred<T> (um Job tipo future)
val deferred: Deferred<Order> = async {
    fetchOrder("ORD-001")
}
val order = deferred.await()  // Suspende até o resultado estar pronto

(3) Composição Concorrente

KOTLIN
suspend fun processOrderConcurrently(id: String): EnrichedOrder = coroutineScope {
    // Lançar em paralelo dentro de coroutineScope
    val orderDeferred = async { fetchOrder(id) }
    val customerDeferred = async { fetchCustomer(id) }
    val inventoryDeferred = async { checkInventory(id) }

    // Aguardar todos os resultados
    val order = orderDeferred.await()
    val customer = customerDeferred.await()
    val inventory = inventoryDeferred.await()

    EnrichedOrder(order, customer, inventory)
}

(4) Comparação launch vs async

Dimensão launch async
Valor de retorno Job Deferred<T>
Obter resultado N/A .await()
Tratamento de exceções Propaga para o pai Armazenado no Deferred
Caso de uso Efeitos colaterais (logging, notificações) Precisa de valor de retorno
Analogia Thread.start() CompletableFuture

7. Tratamento de Exceções em Coroutines

KOTLIN
// try-catch em coroutine
launch {
    try {
        fetchOrder(id)
    } catch (e: Exception) {
        logger.error("Falha ao buscar pedido", e)
    }
}

// CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
    logger.error("Erro na coroutine", exception)
}

launch(handler) {
    fetchOrder(id)  // Exceção não capturada tratada pelo handler
}

// SupervisorJob: falha de um filho não cancela os irmãos
coroutineScope {
    val supervisor = SupervisorJob()
    with(supervisor) {
        launch { throw Exception("Filho 1 falha") }  // Apenas este filho falha
        launch { delay(100); println("Filho 2 ainda executa") }  // Irmão sobrevive
    }
}

8. Exemplo Completo: OrderProcessor Processamento Assíncrono

KOTLIN
// ============================================
// OrderProcessor - Processamento Assíncrono com Coroutines
// Recurso: Enriquecimento concorrente de pedidos
// ============================================

import kotlinx.coroutines.*

data class Order(val id: String, val total: Double, val customerId: String, var customerName: String? = null)
data class Customer(val id: String, val name: String, val tier: String)

// Simular operações assíncronas
suspend fun fetchOrder(id: String): Order {
    delay(100)  // Simular consulta ao banco
    return Order(id, 299.99 + id.substring(4).toInt() * 100, "CUST-${id.substring(4)}")
}

suspend fun fetchCustomer(id: String): Customer {
    delay(150)  // Simular chamada de API
    return Customer(id, "Customer-$id", if (id.endsWith("1")) "VIP" else "STANDARD")
}

suspend fun checkInventory(orderId: String): Boolean {
    delay(80)  // Simular verificação de inventário
    return true
}

// Processamento sequencial
suspend fun processSequential(id: String): Order {
    val order = fetchOrder(id)          // 100ms
    val customer = fetchCustomer(order.customerId)  // 150ms
    val available = checkInventory(id)  // 80ms
    // Total: ~330ms
    return order.copy(customerName = customer.name)
}

// Processamento concorrente
suspend fun processConcurrent(id: String): Order = coroutineScope {
    val orderDeferred = async { fetchOrder(id) }
    val order = orderDeferred.await()

    // Buscar cliente e inventário em paralelo
    val customerDeferred = async { fetchCustomer(order.customerId) }
    val inventoryDeferred = async { checkInventory(id) }

    val customer = customerDeferred.await()
    val available = inventoryDeferred.await()
    // Total: ~100ms + ~150ms = ~250ms (cliente e inventário em paralelo)

    if (!available) throw RuntimeException("Inventário indisponível para $id")
    order.copy(customerName = "${customer.name} (${customer.tier})")
}

// Processamento em lote
suspend fun processBatch(orderIds: List<String>): List<Order> = coroutineScope {
    orderIds.map { id ->
        async { processConcurrent(id) }
    }.awaitAll()
}

fun main() = runBlocking {
    val orderIds = listOf("ORD-001", "ORD-002", "ORD-003", "ORD-004", "ORD-005")

    // Medir sequencial
    val seqStart = System.currentTimeMillis()
    val seqResults = orderIds.map { processSequential(it) }
    val seqTime = System.currentTimeMillis() - seqStart
    println("Sequencial: ${seqTime}ms para ${seqResults.size} pedidos")

    // Medir concorrente
    val conStart = System.currentTimeMillis()
    val conResults = processBatch(orderIds)
    val conTime = System.currentTimeMillis() - conStart
    println("Concorrente: ${conTime}ms para ${conResults.size} pedidos")

    // Imprimir resultados
    println("\n=== Pedidos Processados ===")
    conResults.forEach { order ->
        println("  ${order.id}: \$${order.total} USD | ${order.customerName}")
    }

    // Concorrência estruturada: erro em um cancela todos
    println("\n=== Tratamento de Erros ===")
    try {
        coroutineScope {
            launch { delay(200); println("Tarefa 1 concluída") }
            launch { delay(100); throw RuntimeException("Tarefa 2 falhou!") }
            launch { delay(300); println("Tarefa 3 concluída") }
        }
    } catch (e: RuntimeException) {
        println("Capturado: ${e.message}")
    }
}

Saída:

TEXT 📖 Somente leitura
Sequencial: 1650ms para 5 pedidos
Concorrente: 370ms para 5 pedidos

=== Pedidos Processados ===
  ORD-001: $1100 USD | Customer-CUST-001 (VIP)
  ORD-002: $1200 USD | Customer-CUST-002 (STANDARD)
  ORD-003: $1300 USD | Customer-CUST-003 (STANDARD)
  ORD-004: $1400 USD | Customer-CUST-004 (STANDARD)
  ORD-005: $1500 USD | Customer-CUST-005 (STANDARD)

=== Tratamento de Erros ===
Tarefa 1 concluída
Capturado: Tarefa 2 falhou!

9. Exemplos práticos rápidos

▶ Exemplo: launch e runBlocking

KOTLIN
import kotlinx.coroutines.*

// runBlocking: thread principal espera corrotinas terminarem
fun main() = runBlocking {
    println("Início do main")

    // launch: retorna Job, não bloqueia
    val job = launch {
        delay(500)
        println("Lançado após 500ms")
    }

    println("Continuando no main")
    job.join()
    println("Lançado concluído")
}

// Saída:
// Início do main
// Continuando no main
// Lançado após 500ms
// Lançado concluído

▶ Exemplo: async e await

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    // async retorna Deferred<T> (futuro)
    val time = measureTimeMillis {
        val r1 = async { fetchData("ORD-001") }
        val r2 = async { fetchData("ORD-002") }
        val r3 = async { fetchData("ORD-003") }

        // await suspende até obter o resultado
        val results = listOf(r1.await(), r2.await(), r3.await())
        println("Resultados: $results")
    }
    println("Tempo total: ${time}ms (sequencial seria ~3000ms)")
}

suspend fun fetchData(id: String): String {
    delay(1000)
    return "Resultado($id)"
}

// Saída:
// Resultados: [Resultado(ORD-001), Resultado(ORD-002), Resultado(ORD-003)]
// Tempo total: ~1000ms

▶ Exemplo: Dispatchers e troca de contexto

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    val start = Thread.currentThread().name

    // Dispatador principal (ou Main se disponível)
    val result = withContext(Dispatchers.Default) {
        // CPU-bound executado no pool Default
        val heavyCompute = (1..1_000_000).sum()
        println("Worker thread: ${Thread.currentThread().name}")
        heavyCompute
    }

    println("Original thread: $start")
    println("Resultado: $result")
}

// Saída:
// Worker thread: DefaultDispatcher-worker-1
// Original thread: main
// Resultado: 1784293664...

▶ Exemplo: job e cancelamento

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            println("Trabalho $i")
            delay(100)
        }
    }

    delay(250)
    println("Cancelando...")
    job.cancel()
    job.join()
    println("Cancelado")
}

// Saída:
// Trabalho 0
// Trabalho 1
// Trabalho 2
// Cancelando...
// Cancelado

▶ Exemplo: Exceções em corrotinas

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Capturado: ${exception.message}")
    }

    val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)

    scope.launch {
        try {
            delay(100)
            throw RuntimeException("Falha em tarefa 1")
        } catch (e: Exception) {
            println("Capturado localmente: ${e.message}")
        }
    }

    scope.launch {
        delay(50)
        throw RuntimeException("Falha em tarefa 2")
    }

    delay(1000)
    scope.cancel()
}

// Saída:
// Capturado: Falha em tarefa 2
// Capturado localmente: Falha em tarefa 1

▶ Exemplo: SupervisorJob vs Job normal

KOTLIN
import kotlinx.coroutines.*

fun main() = runBlocking {
    // Sem SupervisorJob: falha de uma tarefa cancela irmãos
    println("=== Job normal ===")
    try {
        coroutineScope {
            launch { delay(100); throw RuntimeException("Erro A") }
            launch { delay(200); println("Tarefa B") }
        }
    } catch (e: Exception) {
        println("Capturado: ${e.message}")
    }

    // Com SupervisorJob: filhos falham independentemente
    println("\n=== SupervisorJob ===")
    supervisorScope {
        launch { delay(100); throw RuntimeException("Erro A") }
        launch { delay(200); println("Tarefa B continua") }
    }
}

// Saída:
// === Job normal ===
// Capturado: Erro A
//
// === SupervisorJob ===
// Capturado: (no handler) Erro A
// Tarefa B continua

▶ Exemplo: Flow basic

KOTLIN
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    // Flow é stream frio - emite valores sob demanda
    val flow = flow {
        for (i in 1..5) {
            delay(100)
            emit(i)
        }
    }

    // collect terminal
    flow.collect { println("Valor: $it") }

    // Operadores intermediários
    val squared = (1..5).asFlow()
        .map { it * it }
        .filter { it > 5 }
        .toList()

    println("Quadrados > 5: $squared")
}

❓ Perguntas Frequentes

P: Qual a diferença entre coroutines e Virtual Threads? R: Coroutines são a implementação em modo usuário do Kotlin requerendo marcadores suspend; Virtual Threads são de nível JVM (JDK 21+) sem necessidade de mudanças no código. Coroutines são mais flexíveis (multiplataforma), enquanto Virtual Threads são mais transparentes (sem mudanças de biblioteca).

P: Qual a diferença entre delay() e Thread.sleep()? R: delay() suspende a coroutine e libera a thread; Thread.sleep() bloqueia toda a thread. Em coroutines, sempre use delay, nunca Thread.sleep.

P: Quando devo usar runBlocking? R: Principalmente em funções main() e testes como ponto de entrada de coroutines. Evite em código de produção — ele bloqueia a thread atual.

P: Como limpar recursos após cancelamento de coroutine? R: Use try-finally ou funções use. Execute limpeza no bloco finally quando uma coroutine é cancelada para garantir liberação de recursos.

P: Quando uma exceção async é lançada? R: A exceção de async é armazenada no Deferred e lançada apenas quando .await() é chamado. Se você nunca aguardar, a exceção é silenciosamente perdida.

P: Quando devo usar SupervisorJob? R: Quando a falha de um filho não deve afetar os irmãos. Por exemplo, em código de UI, uma requisição falhando não deve cancelar as outras. Em um coroutineScope normal, um filho falhando cancela todos os irmãos.


📖 Resumo


📝 Exercícios

  1. Iniciante (⭐): Use runBlocking + launch para iniciar 3 coroutines, cada uma atrasando um tempo diferente e imprimindo uma mensagem. Dica: launch { delay(N); println(...) }
  2. Intermediário (⭐⭐): Use coroutineScope + async para buscar concorrentemente informações de pedido e cliente, combinando-os em um pedido completo. Dica: async { fetchOrder }, await()
  3. Avançado (⭐⭐⭐): Implemente uma função de busca de pedido com timeout e retry: timeout de 3 segundos, até 3 tentativas, backoff exponencial. Dica: withTimeout + loop retry

← Anterior | Próximo →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%