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.

CaracteristicaChannelFlow
TipoHotCold
ProdutoresUm ou váriosUm
ConsumidoresUmUm 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 produce em vez de criar channels manualmente, pois ele garante o fechamento automatico e trata cancelamento corretamente.
  • Use buffer limitado na maioria dos casos. Channel.UNLIMITED pode causar problemas de memória se o produtor for mais rápido que o consumidor.
  • Use Channel.CONFLATED quando 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() apos close() lanca ClosedSendChannelException. Verifique o estado antes ou use trySend().
  • 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 coroutineScope para 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.