Filas de Tarefas para C# (.NET): System.Threading.Channels e TPL Dataflow
Ao desenvolver aplicações multithread em C# (.NET), surge frequentemente a necessidade de organizar a transferência assíncrona de dados entre produtores (producers) e consumidores (consumers). O padrão clássico Producer-Consumer é resolvido com filas de tarefas. No ecossistema .NET, existem duas ferramentas poderosas para isso: System.Threading.Channels (introduzido no .NET Core 3.0) e a biblioteca TPL Dataflow (parte do .NET desde a versão 4.5).
Neste artigo, abordaremos ambas as abordagens, suas características e forneceremos exemplos práticos de código.
1. System.Threading.Channels — Fila leve e rápida
O namespace System.Threading.Channels fornece filas assíncronas e thread-safe, otimizadas para cenários de alta taxa de transferência. Os principais tipos são:
Channel<T>— classe abstrata de canal.Channel.CreateUnbounded<T>()— fila ilimitada (risco de estouro de memória).Channel.CreateBounded<T>(int capacity)— fila limitada com controle de pressão.ChannelWriter<T>— interface para escrita.ChannelReader<T>— interface para leitura.
Exemplo: Producer-Consumer com Channel
Vamos criar um exemplo simples onde um produtor gera números e um consumidor os processa.
using System.Threading.Channels;using System.Threading.Tasks;
var channel = Channel.CreateUnbounded<int>();
// Producervar producer = Task.Run(async () =>{ for (int i = 0; i < 10; i++) { await channel.Writer.WriteAsync(i); Console.WriteLine($"Produzido: {i}"); await Task.Delay(100); } channel.Writer.Complete(); // Sinal de conclusão});
// Consumervar consumer = Task.Run(async () =>{ await foreach (var item in channel.Reader.ReadAllAsync()) { Console.WriteLine($"Processado: {item * 2}"); await Task.Delay(200); }});
await Task.WhenAll(producer, consumer);Console.WriteLine("Pronto!");Vantagens do Channels:
- Overhead mínimo (sem alocação de objetos adicionais).
- Suporte nativo a
async/awaiteIAsyncEnumerable. - Ideal para sistemas de alta carga (ex.: processamento de pacotes de rede).
2. TPL Dataflow — Pipeline de processamento poderoso
A biblioteca System.Threading.Tasks.Dataflow fornece blocos mais complexos para construir pipelines e redes de processamento de dados. Os principais blocos são:
BufferBlock<T>— fila simples.ActionBlock<T>— bloco que executa uma ação em cada elemento.TransformBlock<TInput, TOutput>— transforma cada elemento.BatchBlock<T>— agrupa elementos em lotes.
Exemplo: Pipeline com ActionBlock
Vamos implementar a mesma tarefa: produtor envia números, consumidor processa.
using System.Threading.Tasks.Dataflow;
var actionBlock = new ActionBlock<int>(async item =>{ await Task.Delay(200); Console.WriteLine($"Processado: {item * 2}");}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 2 });
// Producerfor (int i = 0; i < 10; i++){ await actionBlock.SendAsync(i); Console.WriteLine($"Produzido: {i}"); await Task.Delay(100);}
actionBlock.Complete();await actionBlock.Completion;Console.WriteLine("Pronto!");Vantagens do TPL Dataflow:
- Configuração flexível