Filas de Tarefas em C# (.NET): System.Threading.Channels e TPL Dataflow

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

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/await e IAsyncEnumerable.
  • 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

Recommendations