System.Threading.Channels
Crie pipelines produtor-consumidor usando Channel, ChannelWriter e ChannelReader para controlar a contrapressão.
System.Threading.Channels é uma aula grátis de C# Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C# Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C# Academy inclui 4 aulas no total.
O Que São Canais?
System.Threading.Channels (introduzido no .NET Core 3) fornece uma fila de produtor-consumidor de alto desempenho e segura para threads. Ao contrário de BlockingCollection, os canais são totalmente assíncronos e eficientes no uso de alocações — ideais para fluxos de processamento dentro do processo.
Criando um Canal
Os canais são criados com uma fábrica. Escolha unbounded (sem limite) ou bounded (capacidade limitada com contrapressão). A fábrica retorna um Channel<T> com um Writer e um Reader.
using System.Threading.Channels;
// Unbounded: unlimited capacity, no backpressure
var unbounded = Channel.CreateUnbounded<string>();
// Bounded: max 100 items; writer waits when full
var bounded = Channel.CreateBounded<string>(100);
// Bounded with drop-oldest strategy:
var dropping = Channel.CreateBounded<string>(new BoundedChannelOptions(50)
{
FullMode = BoundedChannelFullMode.DropOldest
});Escrevendo em um Canal
Os produtores escrevem itens usando WriteAsync (aguarda se o canal bounded estiver cheio) ou TryWrite (retorna false imediatamente se estiver cheio). Sinalize a conclusão com Writer.Complete().
var channel = Channel.CreateUnbounded<int>();
// Producer task
var producer = Task.Run(async () =>
{
for (int i = 0; i < 100; i++)
{
await channel.Writer.WriteAsync(i);
await Task.Delay(10);
}
channel.Writer.Complete(); // signal no more items
});Lendo de um Canal
Os consumidores leem com ReadAllAsync() (a abordagem mais simples) ou com ReadAsync/TryRead para obter mais controle. ReadAllAsync é concluído quando o escritor chama Complete().
// Consumer task
var consumer = Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
Console.WriteLine($"Processed: {item}");
// Naturally paced — waits for next item
}
Console.WriteLine("Channel completed");
});
await Task.WhenAll(producer, consumer);Vários Produtores
Os canais são seguros para threads. Vários produtores podem escrever simultaneamente — não é necessário usar bloqueios. Chame Writer.Complete() somente depois que todos os produtores terminarem.
var channel = Channel.CreateUnbounded<WorkItem>();
var producers = Enumerable.Range(0, 4).Select(id =>
Task.Run(async () =>
{
for (int i = 0; i < 25; i++)
await channel.Writer.WriteAsync(new WorkItem(id, i));
}));
// Wait for all producers before completing the writer
await Task.WhenAll(producers);
channel.Writer.Complete();Vários Consumidores (Distribuição)
Execute várias tarefas de consumidor no mesmo leitor do canal para paralelizar o processamento. Cada item é entregue exatamente a um consumidor (é particionado, não transmitido para todos).
var channel = Channel.CreateBounded<WorkItem>(100);
// 4 parallel consumers
var consumers = Enumerable.Range(0, 4).Select(id =>
Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
await ProcessItemAsync(item);
Console.WriteLine($"Consumer {id} processed {item.Id}");
}
}));
await Task.WhenAll(consumers);Padrão de Fluxo de Processamento
Encadeie canais em um fluxo de processamento: cada estágio lê de um canal, processa os itens e escreve no próximo. Os estágios são executados simultaneamente, com contrapressão natural.
// Stage 1: raw data
var stage1 = Channel.CreateBounded<string>(50);
// Stage 2: parsed
var stage2 = Channel.CreateBounded<ParsedRecord>(50);
// Stage 3: enriched
var stage3 = Channel.CreateBounded<EnrichedRecord>(50);
var parse = ParseStageAsync(stage1.Reader, stage2.Writer);
var enrich = EnrichStageAsync(stage2.Reader, stage3.Writer);
var persist = PersistStageAsync(stage3.Reader);
await Task.WhenAll(parse, enrich, persist);Contrapressão com Canais bounded
Um canal bounded aplica contrapressão automaticamente: quando o canal está cheio, WriteAsync suspende o produtor até que o consumidor retire itens. Nenhum código manual de limitação é necessário.
// Bounded channel: max 10 items
var channel = Channel.CreateBounded<string>(10);
// Fast producer
var producer = Task.Run(async () =>
{
for (int i = 0; i < 1000; i++)
{
// WriteAsync waits when channel has 10 items
await channel.Writer.WriteAsync($"item-{i}");
// Producer is naturally slowed to consumer speed
}
channel.Writer.Complete();
});
// Slow consumer
var consumer = Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
await Task.Delay(50); // simulate slow processing
Console.WriteLine(item);
}
});Tratamento de Erros em Canais
Passe uma exceção para Writer.Complete(exception) para propagar os erros a todos os leitores que estiverem aguardando. Os leitores recebem a exceção na próxima leitura do canal.
var channel = Channel.CreateUnbounded<int>();
var producer = Task.Run(async () =>
{
try
{
for (int i = 0; i < 100; i++)
{
if (i == 50) throw new Exception("Producer failed at 50");
await channel.Writer.WriteAsync(i);
}
channel.Writer.Complete();
}
catch (Exception ex)
{
channel.Writer.Complete(ex); // propagate to reader
}
});
try
{
await foreach (var item in channel.Reader.ReadAllAsync())
Console.WriteLine(item);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Channel error: {ex.Message}");
}Na Prática: Fila de Tarefas em Segundo Plano
Uma fila de tarefas em segundo plano usando um serviço hospedado e um canal: as solicitações HTTP enfileiram tarefas; um trabalhador em segundo plano as processa uma por vez.
public class JobQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue
= Channel.CreateBounded<Func<CancellationToken, Task>>(100);
public ChannelWriter<Func<CancellationToken, Task>> Writer => _queue.Writer;
public ChannelReader<Func<CancellationToken, Task>> Reader => _queue.Reader;
}
public class JobProcessor : BackgroundService
{
private readonly JobQueue _queue;
public JobProcessor(JobQueue q) => _queue = q;
protected override async Task ExecuteAsync(CancellationToken ct)
{
await foreach (var job in _queue.Reader.ReadAllAsync(ct))
await job(ct);
}
}Verificação Rápida
O que acontece quando um produtor chama WriteAsync em um canal bounded cheio?
Recapitulação: System.Threading.Channels
Principais conclusões:
- Os canais fornecem uma fila de produtor-consumidor de alto desempenho e nativamente assíncrona
- Unbounded: sem limite; Bounded: capacidade finita com contrapressão ou estratégias de descarte
- Vários produtores e consumidores são seguros sem o uso de bloqueios
- ReadAllAsync() + await foreach = padrão de consumo mais simples
- Encadeie canais em fluxos de processamento para obter processamento concorrente por estágios
- Chame Writer.Complete() (ou Complete(exception)) para sinalizar o fim do fluxo
Perguntas Frequentes
A aula “System.Threading.Channels” é grátis?
Sim — o texto completo de “System.Threading.Channels” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C# Academy, atualize para CoddyKit PRO. O curso de C# Academy inclui 4 aulas no total.
O que vou aprender em “System.Threading.Channels”?
Crie pipelines produtor-consumidor usando Channel, ChannelWriter e ChannelReader para controlar a contrapressão. Você pratica C# Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar C# Academy?
Nenhuma experiência prévia é necessária. C# Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.
Quanto tempo leva a aula “System.Threading.Channels”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de C# Academy?
Sim. Cada aula de C# Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- IAsyncEnumerable e await foreach
- System.Threading.Channels
- ValueTask e como evitar alocações
- ConfigureAwait e contexto de sincronização