Introducción a las colas de tareas en C#
En la programación multiproceso moderna, la cola de tareas (task queue) es un patrón fundamental para organizar el procesamiento asíncrono de datos. En C# (.NET), este patrón se implementa a través de varios mecanismos potentes que permiten a los desarrolladores gestionar eficientemente los hilos de ejecución y distribuir la carga de trabajo.
La cola de tareas resuelve el problema clásico de productor-consumidor (producer-consumer), donde una parte del código crea tareas y otra las procesa. En este artículo, exploraremos las principales formas de implementar colas de tareas en C#: desde colecciones simples hasta canales modernos (Channel).
Enfoques principales para implementar colas de tareas
1. ConcurrentQueue — cola thread-safe
La implementación más simple y de bajo nivel es ConcurrentQueue<T> del espacio de nombres System.Collections.Concurrent. Proporciona los métodos thread-safe Enqueue y TryDequeue.
using System;using System.Collections.Concurrent;using System.Threading.Tasks;
public class SimpleTaskQueue{ private readonly ConcurrentQueue<Action> _tasks = new(); private readonly Task _worker;
public SimpleTaskQueue() { _worker = Task.Run(ProcessTasks); }
public void Enqueue(Action task) { _tasks.Enqueue(task); }
private void ProcessTasks() { while (true) { if (_tasks.TryDequeue(out var task)) { task(); } } }}Ventajas:
- Simplicidad de uso
- Bajo consumo de memoria
- Thread-safe sin bloqueos
Desventajas:
- Espera activa (busy waiting) en el bucle
- Falta de señalización sobre nuevas tareas
- Dificultad para detenerlo
2. BlockingCollection — cola con bloqueo
BlockingCollection<T> resuelve el problema de la espera activa. Proporciona los métodos Add y Take, que bloquean el hilo hasta que aparezcan datos.
using System;using System.Collections.Concurrent;using System.Threading.Tasks;
public class BlockingTaskQueue : IDisposable{ private readonly BlockingCollection<Action> _tasks = new(new ConcurrentQueue<Action>()); private readonly Task[] _workers; private bool _disposed;
public BlockingTaskQueue(int workerCount = 4) { _workers = new Task[workerCount]; for (int i = 0; i < workerCount; i++) { _workers[i] = Task.Run(ProcessTasks); } }
public void Enqueue(Action task) { _tasks.Add(task); }
private void ProcessTasks() { foreach (var task in _tasks.GetConsumingEnumerable()) { if (_disposed) break; task(); } }
public void CompleteAdding() { _tasks.CompleteAdding(); }
public void Dispose() { _disposed = true; _tasks.CompleteAdding(); Task.WaitAll(_workers); _tasks.Dispose(); }}Características clave:
- Bloqueo automático del hilo cuando no hay tareas
- Soporte para cancelación mediante
CancellationToken - Posibilidad de limitar la capacidad de la cola
- Finalización segura mediante
CompleteAdding
Enfoque moderno: Channel<T>
A partir de .NET Core 3.0 y .NET 5, en C# apareció System.Threading.Channels.Channel<T> — esta es la implementación más eficiente y flexible de una cola de tareas. Soporta operaciones asíncronas, cancelación y limitación del rendimiento.