launch/join vs async/await em Kotlin: o que escolher em 2025?

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

launch/join vs async/await em Kotlin: o que escolher em 2025? Análise completa com exemplos

\n\n

Olá, colega! Se você já escreveu coroutines em Kotlin, certamente já se perguntou: "Qual é, afinal, a diferença entre launch e async? E para que serve esse misterioso join?". Essa pergunta é um verdadeiro sucesso no Stack Overflow, com milhares de visualizações. E não é à toa: à primeira vista, ambas as ferramentas fazem a mesma coisa — iniciam uma tarefa em segundo plano. Mas o diabo, como sempre, está nos detalhes.

\n\n

Em 2025, quando Kotlin coroutines se tornaram mainstream no desenvolvimento Android e no backend, entender essas diferenças não é apenas um capricho, mas uma necessidade para escrever código limpo, seguro e eficiente. Neste artigo, não vamos apenas listar fatos secos. Vamos analisar cenários reais, olhar para o código e extrair as melhores práticas que você pode aplicar com segurança em seus projetos agora mesmo.

\n\n

Vamos nessa!

\n\n

launch: "Iniciou e esqueceu"

\n\n

A função launch é, provavelmente, a maneira mais simples de iniciar uma coroutine. Ela cria uma nova coroutine que não retorna um resultado. Sua tarefa é executar algum trabalho: atualizar a UI, enviar um log, gravar dados em um arquivo. O tipo do valor retornado é Job. É como dar uma tarefa a um entregador: "Leve o pacote e volte, não preciso de relatório".

\n\n

O método join permite aguardar a conclusão dessa coroutine. Ele suspende a execução da coroutine atual até que o job que chamou join seja concluído.

\n\n

Vamos considerar um exemplo. Suponha que precisamos carregar duas imagens em paralelo e depois combiná-las. Usando launch e join, ficaria assim:

\n\n
import kotlinx.coroutines.*\n\nsuspend fun loadImage(url: String): Image {\n    // simulação de carregamento\n    delay(1000)\n    return Image(url)\n}\n\ndata class Image(val url: String)\n\nsuspend fun loadTwoImagesWithLaunch() = coroutineScope {\n    var image1: Image? = null\n    var image2: Image? = null\n\n    val job1 = launch {\n        image1 = loadImage("https://example.com/img1.png")\n    }\n\n    val job2 = launch {\n        image2 = loadImage("https://example.com/img2.png")\n    }\n\n    // Aguardamos ambas as tarefas\n    job1.join()\n    job2.join()\n\n    // Agora image1 e image2 estão garantidamente carregadas\n    println("Images loaded: $image1 and $image2")\n}
\n\n

O que é importante aqui? Usamos variáveis mutáveis (var) para armazenar o resultado. Isso não é muito elegante e pode levar a problemas com threads, embora as coroutines operem dentro de uma mesma thread. Mas a principal desvantagem é que não podemos usar launch para retornar um valor diretamente.

\n\n

async: "Iniciou e obtenha o resultado"

\n\n

Já o async é a "classe executiva". Ele cria uma coroutine que retorna um resultado na forma de um objeto Deferred<T>. É como pedir um táxi: você espera, e recebe não apenas o fato da viagem, mas um resultado concreto — você chegou ao destino. Para obter esse resultado, usa-se await().

\n\n

O mesmo exemplo de carregamento de imagens, mas usando async/await, fica mais elegante:

\n\n
import kotlinx.coroutines.*\n\nsuspend fun loadImage(url: String): Image {\n    delay(1000)\n    return Image(url)\n}\n\ndata class Image(val url: String)\n\nsuspend fun loadTwoImagesWithAsync() = coroutineScope {\n    val deferred1 = async { loadImage("https://example.com/img1.png") }\n    val deferred2 = async { loadImage("https://example.com/img2.png") }\n\n    // await() suspende a coroutine até obter o resultado\n    val image1 = deferred1.await()\n    val image2 = deferred2.await()\n\n    println("Images loaded: $image1 and $image2")\n}
\n\n

Sente a diferença? Nada de var, nada de gerenciamento manual. Tudo limpo,

Blogs

Book Recommendations