O que é launch em Kotlin?

O launch é um coroutine builder que inicia uma nova coroutine sem retornar resultado. Ele é do tipo “fire-and-forget” — você dispara a coroutine e segue em frente. Perfeito pra tarefas que precisam rodar em background mas não precisam devolver um valor.

O launch retorna um Job, que permite controlar o ciclo de vida da coroutine: cancelar, esperar terminar ou verificar o status.

Exemplo básico

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        println("Coroutine iniciada")
        delay(1000)
        println("Coroutine finalizada")
    }

    println("Código principal continua...")
    job.join() // Espera a coroutine terminar
}

Saída:

Código principal continua...
Coroutine iniciada
Coroutine finalizada

Controlando com Job

O Job retornado pelo launch oferece várias possibilidades:

import kotlinx.coroutines.*

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

    delay(1000)
    println("Cansou de esperar!")
    job.cancel()
    println("Job cancelado: ${job.isCancelled}")
}

Dispatchers

Você pode escolher em qual thread a coroutine vai rodar:

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch(Dispatchers.Default) {
        println("CPU-intensivo em: ${Thread.currentThread().name}")
    }

    launch(Dispatchers.IO) {
        println("Operação de I/O em: ${Thread.currentThread().name}")
    }
}
  • Dispatchers.Default — tarefas pesadas de CPU
  • Dispatchers.IO — operações de entrada/saída
  • Dispatchers.Main — thread principal (Android)

launch vs async

A diferença é simples: launch não retorna valor, async retorna. Se você precisa do resultado da coroutine, use async. Se é só pra executar algo no background, launch resolve.

// Não precisa do resultado? Use launch
launch { salvarNoLog("evento_123") }

// Precisa do resultado? Use async
val dados = async { buscarDados() }
println(dados.await())

Structured Concurrency com launch

O launch respeita o conceito de structured concurrency do Kotlin. Isso significa que uma coroutine filha está vinculada ao escopo pai. Se o escopo pai for cancelado, todas as coroutines filhas também são canceladas automaticamente.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val escopo = CoroutineScope(Dispatchers.Default)

    val jobPai = escopo.launch {
        launch {
            delay(2000)
            println("Filha 1 terminou") // Nunca será impresso
        }
        launch {
            delay(2000)
            println("Filha 2 terminou") // Nunca será impresso
        }
    }

    delay(500)
    jobPai.cancel() // Cancela o pai e todas as filhas
    println("Todas as coroutines foram canceladas")
}

Tratamento de exceções com launch

Diferente do async, onde exceções são lançadas ao chamar await(), no launch as exceções propagam imediatamente para o escopo pai. Para interceptá-las, use um CoroutineExceptionHandler:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, excecao ->
        println("Exceção capturada: ${excecao.message}")
    }

    val escopo = CoroutineScope(Dispatchers.Default + handler)

    escopo.launch {
        throw RuntimeException("Algo deu errado!")
    }

    delay(500) // Aguarda para ver a saída
}

Casos de Uso no Mundo Real

  • Envio de analytics e telemetria: disparar eventos de rastreamento sem bloquear a interface do usuário. O resultado do envio não importa para o fluxo principal da aplicação.
  • Sincronização periódica de dados: usar launch dentro de um loop com delay para sincronizar dados com o servidor a cada intervalo de tempo, como atualizar cache local.
  • Processamento de filas: consumir itens de uma fila (mensagens, notificações, tarefas) em background, processando cada item de forma independente sem precisar do resultado.
  • Atualização de UI no Android: disparar operações de atualização de tela a partir do viewModelScope.launch, garantindo que a coroutine seja cancelada automaticamente quando o ViewModel for destruído.

Boas Práticas

  • Sempre use launch dentro de um CoroutineScope bem definido. Evite usar GlobalScope.launch, pois ele não respeita o ciclo de vida da aplicação e pode causar vazamentos de memória.
  • Prefira Dispatchers.IO para operações de rede e disco, e Dispatchers.Default para cálculos pesados. Nunca faça operações bloqueantes no Dispatchers.Main.
  • Utilize job.join() quando precisar garantir que a coroutine terminou antes de prosseguir, mas evite usá-lo excessivamente — isso anula a vantagem da concorrência.
  • Trate exceções com CoroutineExceptionHandler ou blocos try/catch dentro do launch para evitar crashes inesperados.
  • Use supervisorScope quando quiser que a falha de uma coroutine filha não cancele as irmãs.

Erros Comuns

  • Usar GlobalScope.launch indiscriminadamente: isso cria coroutines que vivem durante toda a aplicação, podendo causar vazamentos de memória e comportamentos inesperados.
  • Esquecer de cancelar coroutines: não vincular o launch a um escopo com ciclo de vida definido faz com que coroutines continuem rodando mesmo depois que a tela ou componente foi destruído.
  • Ignorar exceções: como o launch propaga exceções para o escopo pai, não tratá-las pode derrubar toda a aplicação silenciosamente.
  • Bloquear a thread dentro do launch: usar Thread.sleep() em vez de delay() bloqueia a thread e desperdiça os benefícios das coroutines.
  • Confundir launch com async: tentar obter um resultado de launch não funciona. Se precisa de retorno, use async com await().

Perguntas Frequentes

O launch bloqueia a thread? Não. O launch inicia a coroutine de forma assíncrona e retorna imediatamente um Job. A thread que chamou o launch continua executando o código seguinte sem esperar.

Quando usar launch em vez de async? Use launch quando a tarefa não precisa devolver um resultado para quem a iniciou. Exemplos: salvar logs, enviar eventos de analytics, atualizar cache. Se precisa do valor de retorno, use async.

O que acontece se uma exceção ocorrer dentro do launch? A exceção é propagada para o escopo pai. Se não houver tratamento, ela pode cancelar todo o escopo e suas coroutines filhas. Use CoroutineExceptionHandler ou try/catch interno para lidar com isso.

Posso usar launch fora de um CoroutineScope? Não. O launch é uma função de extensão de CoroutineScope. Você precisa de um escopo como runBlocking, viewModelScope, lifecycleScope ou um CoroutineScope customizado.

Termos Relacionados

  • Coroutine — o conceito fundamental de concorrência leve em Kotlin
  • Suspend — funções que podem ser pausadas e retomadas dentro de coroutines
  • Async — coroutine builder que retorna um resultado via Deferred
  • Flow — streams assíncronos reativos baseados em coroutines

launch é provavelmente o coroutine builder que você mais vai usar no dia a dia.