O que é Channel em Kotlin?
Um Channel em Kotlin é uma estrutura para comunicação entre coroutines. Funciona como uma fila: uma coroutine envia dados pelo canal e outra recebe do outro lado. E o jeito mais seguro de trocar informações entre coroutines sem problemas de concorrencia. O conceito foi diretamente inspirado pelos channels de Go — a implementação em Kotlin segue padrões similares, mas adaptados para o ecossistema de coroutines da JVM.
Se o Flow e como uma torneira (um produtor, um consumidor), o Channel é mais como um cano de comunicação bidirecional entre partes independentes do código.
Imagine uma esteira de fabrica: de um lado, um operario coloca pecas na esteira (send), e do outro lado, outro operario retira as pecas para montar o produto (receive). A esteira tem uma capacidade limitada – se estiver cheia, o primeiro operario espera; se estiver vazia, o segundo espera. E exatamente assim que um Channel funciona.
Exemplo básico
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
val canal = Channel<String>()
launch {
canal.send("Primeira mensagem")
canal.send("Segunda mensagem")
canal.close()
}
for (msg in canal) {
println("Recebido: $msg")
}
}
O send envia dados para o canal e o for consome. Quando o canal e fechado com close(), o loop encerra automaticamente.
Tipos de Channel
Kotlin oferece diferentes capacidades de buffer:
// Sem buffer -- send suspende ate alguem fazer receive
val rendezvous = Channel<Int>()
// Buffer limitado
val buffered = Channel<Int>(capacity = 10)
// Buffer ilimitado
val unlimited = Channel<Int>(Channel.UNLIMITED)
// Mantem so o ultimo valor
val conflated = Channel<Int>(Channel.CONFLATED)
Produtor e consumidor
O padrão produtor-consumidor fica bem elegante com produce:
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun CoroutineScope.produzirNumeros() = produce {
for (i in 1..5) {
delay(300)
send(i)
}
}
fun main() = runBlocking {
val numeros = produzirNumeros()
for (n in numeros) {
println("Numero: $n")
}
}
Channel vs Flow
A principal diferenca e que Channel e hot – ele produz dados independente de ter alguem consumindo. Ja o Flow e cold – só produz quando alguem coleta.
| Caracteristica | Channel | Flow |
|---|---|---|
| Tipo | Hot | Cold |
| Produtores | Um ou vários | Um |
| Consumidores | Um | Um ou vários |
Use Channel quando precisar de comunicação ativa entre coroutines. Para streams de dados reativos, prefira Flow.
Pipeline com Channels
Channels permitem criar pipelines onde cada estagio processa dados e passa para o proximo, similar a um fluxo de producao industrial:
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun CoroutineScope.gerarPedidos() = produce {
val pedidos = listOf("Pedido-001", "Pedido-002", "Pedido-003", "Pedido-004")
for (pedido in pedidos) {
delay(200)
send(pedido)
}
}
fun CoroutineScope.validarPedidos(entrada: ReceiveChannel<String>) = produce {
for (pedido in entrada) {
delay(150)
send("$pedido [VALIDADO]")
}
}
fun CoroutineScope.processarPagamento(entrada: ReceiveChannel<String>) = produce {
for (pedido in entrada) {
delay(300)
send("$pedido [PAGO]")
}
}
fun main() = runBlocking {
val pedidos = gerarPedidos()
val validados = validarPedidos(pedidos)
val pagos = processarPagamento(validados)
for (resultado in pagos) {
println(resultado)
}
}
Fan-out: múltiplos consumidores
Quando você tem trabalho pesado, pode distribuir itens de um canal entre várias coroutines trabalhadoras:
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
val tarefas = Channel<Int>(capacity = 10)
// Produtor: envia 20 tarefas
launch {
for (i in 1..20) {
tarefas.send(i)
}
tarefas.close()
}
// 3 consumidores processando em paralelo
repeat(3) { trabalhadorId ->
launch {
for (tarefa in tarefas) {
delay(200) // simula processamento
println("Trabalhador $trabalhadorId processou tarefa $tarefa")
}
}
}
}
Fan-in: múltiplos produtores
O cenário inverso também e comum – vários produtores enviando para um único canal:
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
suspend fun produzirLogs(canal: SendChannel<String>, origem: String, quantidade: Int) {
repeat(quantidade) { i ->
delay(100)
canal.send("[$origem] Log #$i")
}
}
fun main() = runBlocking {
val logs = Channel<String>(Channel.UNLIMITED)
launch { produzirLogs(logs, "SERVIDOR-A", 3) }
launch { produzirLogs(logs, "SERVIDOR-B", 3) }
launch { produzirLogs(logs, "SERVIDOR-C", 3) }
// Aguarda e depois fecha
launch {
delay(500)
logs.close()
}
for (log in logs) {
println(log)
}
}
Casos de Uso no Mundo Real
- Filas de processamento: sistemas que recebem eventos (cliques, logs, transacoes) e os processam de forma assíncrona usando channels como fila intermediaria.
- Web scraping paralelo: um produtor gera URLs para visitar e múltiplos consumidores fazem as requisicoes em paralelo, reportando resultados em outro channel.
- Chat em tempo real: mensagens enviadas por usuários sao colocadas em um channel e distribuidas para todos os participantes da sala.
- Processamento de arquivos: leitura de linhas de um arquivo grande em um channel, com múltiplos trabalhadores processando cada linha em paralelo.
Boas Praticas
- Sempre feche o channel com
close()quando o produtor terminar, para que consumidores saibam que não havera mais dados. - Prefira
produceem vez de criar channels manualmente, pois ele garante o fechamento automatico e trata cancelamento corretamente. - Use buffer limitado na maioria dos casos.
Channel.UNLIMITEDpode causar problemas de memória se o produtor for mais rápido que o consumidor. - Use
Channel.CONFLATEDquando você só se importa com o valor mais recente, como atualizações de posicao GPS. - Considere Flow antes de Channel. Muitas vezes um Flow resolve o problema de forma mais simples e com menos risco de vazamento de recursos.
Erros Comuns
- Esquecer de fechar o channel: se o produtor não chama
close(), o consumidor ficara suspenso para sempre esperando mais dados, causando travamento. - Enviar para um channel fechado: chamar
send()aposclose()lancaClosedSendChannelException. Verifique o estado antes ou usetrySend(). - Usar Channel quando Flow resolve: Channel e mais complexo e exige gerenciamento manual de ciclo de vida. Se você só precisa de um stream de dados cold, use Flow.
- Ignorar cancelamento: quando uma coroutine consumidora e cancelada, o channel pode ficar com dados não processados. Estruture o código com
coroutineScopepara garantir limpeza adequada.
Perguntas Frequentes
Qual a diferenca entre Channel e Flow? Channel e hot (produz dados independente de consumidores) e suporta múltiplos produtores. Flow e cold (só produz quando coletado) e e mais simples de usar. Prefira Flow para streams reativos e Channel para comunicação ativa entre coroutines.
Posso ter vários consumidores no mesmo Channel?
Sim, mas cada item sera recebido por apenas um consumidor (fan-out). Se você precisa que todos os consumidores recebam todos os itens, use BroadcastChannel ou SharedFlow.
O que acontece se o buffer do Channel estiver cheio?
A coroutine que chama send() sera suspensa até que haja espaco no buffer. Com trySend(), a operação retorna imediatamente com um resultado indicando se o envio foi bem-sucedido.
Channels podem causar vazamento de memória? Sim, se você criar um Channel com buffer ilimitado e o produtor for muito mais rápido que o consumidor, os dados acumulam na memória. Use buffers limitados e monitore o fluxo de dados.