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
suspendsó podem ser chamadas de coroutines ou de outras funçõessuspend. suspendnã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
suspenddiretamente.
Casos de Uso no Mundo Real
- Chamadas de API REST: Funções
suspendsão ideais para requisições HTTP. Bibliotecas como Ktor Client e Retrofit com suporte a coroutines expõem suas operações como funçõessuspend, 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
suspendusandowithContext(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
suspendcombinadas comFlowpermitem processar item a item sem consumir memória excessiva.
Boas Práticas
- Use
withContextpara trocar de dispatcher: Quando uma funçãosuspendprecisa fazer I/O ou computação pesada, envolva o trecho comwithContext(Dispatchers.IO)ouwithContext(Dispatchers.Default)para não bloquear o dispatcher atual. - Mantenha funções suspend seguras para a main thread: Uma função
suspendbem escrita pode ser chamada de qualquer dispatcher sem risco. Ela mesma deve cuidar de trocar para o dispatcher adequado internamente. - Prefira structured concurrency: Use
coroutineScopedentro de funçõessuspendquando precisar lançar coroutines filhas. Isso garante que todas terminem antes da função retornar. - Não marque como
suspendsem necessidade: Só adicionesuspendquando a função realmente precisa chamar outra função suspensa. Marcar funções desnecessariamente comosuspendforç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
@Throwsou comentários.
Erros Comuns
- Chamar função suspend fora de uma coroutine: O erro mais frequente de quem começa. Funções
suspendprecisam ser chamadas dentro derunBlocking,launch,asyncou outra funçãosuspend. O compilador vai reclamar se você tentar chamar de uma função normal. - Usar
runBlockingem produção:runBlockingbloqueia a thread atual, anulando a vantagem das coroutines. Use apenas emmain()ou em testes. Em código de produção, prefiralaunchouasync. - 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çãosuspendsem usarwithContext(Dispatchers.IO)pode travar o dispatcher principal. - Ignorar o cancelamento: Funções
suspenddevem respeitar o cancelamento da coroutine. Se você faz loops longos, verifiqueisActiveou useensureActive()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, usesuspendCancellableCoroutinepara 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
suspendpara 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.