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/awaiteIAsyncEnumerable. - 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
También en la biblioteca
Migraciones de bases de datos en TypeScript: guía completa de herramientas y enfoques
Rate Limiting en Kotlin: bibliotecas y ejemplos prácticos
inversify para TypeScript: guía completa con ejemplos
Generación de informes PDF en JavaScript (Node.js): Puppeteer, PDFKit, jsPDF