O que é suspend em Kotlin?

A palavra-chave suspend marca uma função que pode ser pausada e retomada sem bloquear a thread em que está rodando. É o coração do sistema de coroutines do Kotlin.

Uma função suspend só pode ser chamada dentro de outra função suspend ou dentro de uma coroutine. O compilador garante isso em tempo de compilação, então não tem como errar.

Sintaxe básica

import kotlinx.coroutines.*

suspend fun buscarUsuario(id: Int): String {
    delay(1000) // Simula uma operacao demorada
    return "Usuário #$id"
}

fun main() = runBlocking {
    val usuario = buscarUsuario(42)
    println(usuario) // Usuário #42
}

A função buscarUsuario é suspensa — ela pode pausar no delay e liberar a thread para fazer outras coisas. Quando o tempo passa, ela retoma de onde parou.

O que acontece por baixo dos panos?

Quando o compilador encontra uma função suspend, ele transforma ela numa máquina de estados usando continuations. Na prática, a função é dividida em pedaços que podem ser executados em momentos diferentes.

Você não precisa se preocupar com esses detalhes pra usar, mas é bom saber que não tem mágica — é tudo gerado em tempo de compilação.

Combinando funções suspend

import kotlinx.coroutines.*

suspend fun buscarNome(): String {
    delay(500)
    return "Fernanda"
}

suspend fun buscarCidade(): String {
    delay(500)
    return "Recife"
}

fun main() = runBlocking {
    val inicio = System.currentTimeMillis()

    val nome = buscarNome()
    val cidade = buscarCidade()

    val tempo = System.currentTimeMillis() - inicio
    println("$nome mora em $cidade (${tempo}ms)")
    // Fernanda mora em Recife (~1000ms - sequencial)
}

No exemplo acima, as chamadas são sequenciais. Se quiser executar em paralelo, use async — aí o tempo cai pela metade.

Execução paralela com async

Quando duas funções suspend são independentes entre si, você pode executá-las em paralelo usando async e await. Isso reduz o tempo total de execução significativamente.

import kotlinx.coroutines.*

suspend fun buscarPedidos(userId: Int): List<String> {
    delay(800)
    return listOf("Pedido #101", "Pedido #102")
}

suspend fun buscarNotificacoes(userId: Int): List<String> {
    delay(600)
    return listOf("Novo comentário", "Promoção disponível")
}

fun main() = runBlocking {
    val inicio = System.currentTimeMillis()

    val pedidos = async { buscarPedidos(1) }
    val notificacoes = async { buscarNotificacoes(1) }

    println("Pedidos: ${pedidos.await()}")
    println("Notificações: ${notificacoes.await()}")

    val tempo = System.currentTimeMillis() - inicio
    println("Tempo total: ${tempo}ms") // ~800ms em vez de ~1400ms
}

Nesse exemplo, ambas as chamadas iniciam ao mesmo tempo. O tempo total é determinado pela operação mais lenta, não pela soma das duas.

Funções suspend com tratamento de erros

Funções suspend funcionam perfeitamente com try-catch, o que torna o tratamento de erros muito mais natural do que callbacks aninhados.

import kotlinx.coroutines.*
import java.io.IOException

suspend fun buscarDadosDoServidor(): String {
    delay(500)
    // Simulando uma falha de rede
    throw IOException("Falha na conexão")
}

fun main() = runBlocking {
    try {
        val dados = buscarDadosDoServidor()
        println(dados)
    } catch (e: IOException) {
        println("Erro ao buscar dados: ${e.message}")
        // Erro ao buscar dados: Falha na conexão
    }
}

Regras importantes

  • Funções suspend só podem ser chamadas de coroutines ou de outras funções suspend.
  • suspend não torna a função assíncrona por si só — ela apenas permite a suspensão.
  • Funções normais não podem chamar funções suspend diretamente.

Casos de Uso no Mundo Real

  • Chamadas de API REST: Funções suspend são ideais para requisições HTTP. Bibliotecas como Ktor Client e Retrofit com suporte a coroutines expõem suas operações como funções suspend, permitindo fazer chamadas de rede sem bloquear a thread principal.
  • Acesso a banco de dados: O Room (Android) e o Exposed (backend) permitem definir queries como funções suspend. Isso garante que consultas pesadas não travem a interface do usuário nem bloqueiem threads do servidor.
  • Leitura e escrita de arquivos: Operações de I/O demoradas podem ser encapsuladas em funções suspend usando withContext(Dispatchers.IO), mantendo a thread principal livre para outras tarefas.
  • Processamento em lote: Quando você precisa processar milhares de itens (enviar emails, gerar relatórios), funções suspend combinadas com Flow permitem processar item a item sem consumir memória excessiva.

Boas Práticas

  • Use withContext para trocar de dispatcher: Quando uma função suspend precisa fazer I/O ou computação pesada, envolva o trecho com withContext(Dispatchers.IO) ou withContext(Dispatchers.Default) para não bloquear o dispatcher atual.
  • Mantenha funções suspend seguras para a main thread: Uma função suspend bem escrita pode ser chamada de qualquer dispatcher sem risco. Ela mesma deve cuidar de trocar para o dispatcher adequado internamente.
  • Prefira structured concurrency: Use coroutineScope dentro de funções suspend quando precisar lançar coroutines filhas. Isso garante que todas terminem antes da função retornar.
  • Não marque como suspend sem necessidade: Só adicione suspend quando a função realmente precisa chamar outra função suspensa. Marcar funções desnecessariamente como suspend força quem chama a usar coroutines sem motivo.
  • Documente o dispatcher esperado: Se a função precisa ser chamada de um dispatcher específico, deixe isso claro na documentação com @Throws ou comentários.

Erros Comuns

  • Chamar função suspend fora de uma coroutine: O erro mais frequente de quem começa. Funções suspend precisam ser chamadas dentro de runBlocking, launch, async ou outra função suspend. O compilador vai reclamar se você tentar chamar de uma função normal.
  • Usar runBlocking em produção: runBlocking bloqueia a thread atual, anulando a vantagem das coroutines. Use apenas em main() ou em testes. Em código de produção, prefira launch ou async.
  • Esquecer de trocar o dispatcher para I/O: Chamar operações de I/O bloqueantes (como leitura de arquivo com java.io.File) dentro de uma função suspend sem usar withContext(Dispatchers.IO) pode travar o dispatcher principal.
  • Ignorar o cancelamento: Funções suspend devem respeitar o cancelamento da coroutine. Se você faz loops longos, verifique isActive ou use ensureActive() para que o cancelamento funcione corretamente.
  • Misturar callbacks com suspend: Evite usar callbacks dentro de funções suspend. Se precisa integrar com APIs baseadas em callback, use suspendCancellableCoroutine para convertê-las corretamente.

Perguntas Frequentes

Qual a diferença entre suspend e async? suspend é um modificador de função que indica que ela pode ser pausada. async é um builder de coroutine que executa uma função suspend e retorna um Deferred com o resultado. São conceitos complementares: suspend define a capacidade de pausar, e async é uma das formas de executar funções suspensas.

Posso chamar uma função suspend de Java? Não diretamente. O compilador Kotlin transforma funções suspend adicionando um parâmetro Continuation extra. Para chamá-las de Java, você precisa trabalhar com a API de Continuation manualmente ou usar wrappers que convertem para CompletableFuture.

Funções suspend rodam em outra thread automaticamente? Não. Uma função suspend roda no dispatcher da coroutine que a chamou. Se precisa rodar em outra thread, use withContext para especificar o dispatcher desejado. A suspensão em si não implica troca de thread.

Quantas funções suspend posso ter rodando ao mesmo tempo? Não há limite prático imposto pela linguagem. Coroutines são muito leves (ocupam poucos bytes de memória), então é possível ter milhares ou até milhões de coroutines simultâneas, ao contrário de threads que consomem muito mais recursos.

Termos Relacionados

  • Coroutines — O mecanismo de concorrência do Kotlin que utiliza funções suspend.
  • Flow — Streams assíncronos construídos sobre funções suspend.
  • val — Declaração de variáveis somente leitura, frequentemente usada para armazenar resultados de funções suspend.
  • fun — A palavra-chave para declarar funções em Kotlin, combinada com suspend para criar funções suspensas.

suspend é a base de tudo que envolve concorrência em Kotlin. Dominar esse conceito abre as portas para coroutines, Flow e muito mais.