Colas de tareas en C# (.NET): System.Threading.Channels y TPL Dataflow

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Colas de tareas para C# (.NET): System.Threading.Channels y TPL Dataflow

Al desarrollar aplicaciones multiproceso en C# (.NET), a menudo surge la necesidad de organizar la transferencia asíncrona de datos entre productores (producers) y consumidores (consumers). El patrón clásico Productor-Consumidor se resuelve mediante colas de tareas. En el ecosistema .NET existen dos herramientas potentes para esto: System.Threading.Channels (apareció en .NET Core 3.0) y la biblioteca TPL Dataflow (parte de .NET desde 4.5).

En este artículo analizaremos ambos enfoques, sus características y proporcionaremos ejemplos prácticos de código.



1. System.Threading.Channels — una cola ligera y rápida

El espacio de nombres System.Threading.Channels proporciona colas asíncronas seguras para subprocesos, optimizadas para escenarios de alto rendimiento. Los tipos principales son:

  • Channel<T> — clase abstracta de canal.
  • Channel.CreateUnbounded<T>() — cola ilimitada (riesgo de desbordamiento de memoria).
  • Channel.CreateBounded<T>(int capacity) — cola limitada con control de presión.
  • ChannelWriter<T> — interfaz para escritura.
  • ChannelReader<T> — interfaz para lectura.


Ejemplo: Productor-Consumidor con Channel

Creemos un ejemplo simple donde un productor genera números y un consumidor los procesa.

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($"Producido: {i}"); await Task.Delay(100); } channel.Writer.Complete(); // Señal de finalización});

// Consumervar consumer = Task.Run(async () =>{ await foreach (var item in channel.Reader.ReadAllAsync()) { Console.WriteLine($"Procesado: {item * 2}"); await Task.Delay(200); }});

await Task.WhenAll(producer, consumer);Console.WriteLine("¡Listo!");

Ventajas de Channels:

  • Gastos generales mínimos (sin asignación de objetos adicionales).
  • Soporte integrado para async/await e IAsyncEnumerable.
  • Ideal para sistemas de alta carga (por ejemplo, procesamiento de paquetes de red).


2. TPL Dataflow — potente canalización de procesamiento

La biblioteca System.Threading.Tasks.Dataflow proporciona bloques más complejos para construir canalizaciones (pipeline) y redes de procesamiento de datos. Los bloques principales son:

  • BufferBlock<T> — cola simple.
  • ActionBlock<T> — bloque que ejecuta una acción sobre cada elemento.
  • TransformBlock<TInput, TOutput> — transforma cada elemento.
  • BatchBlock<T> — agrupa elementos en lotes.


Ejemplo: Canalización con ActionBlock

Implementemos la misma tarea: el productor envía números, el consumidor los procesa.

using System.Threading.Tasks.Dataflow;

var actionBlock = new ActionBlock<int>(async item =>{ await Task.Delay(200); Console.WriteLine($"Procesado: {item * 2}");}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 2 });

// Producerfor (int i = 0; i < 10; i++){ await actionBlock.SendAsync(i); Console.WriteLine($"Producido: {i}"); await Task.Delay(100);}

actionBlock.Complete();await actionBlock.Completion;Console.WriteLine("¡Listo!");

Ventajas de TPL Dataflow:

  • Configuración flexible

Recommendations