Introdução às filas de tarefas em C#
Na programação multithread moderna, a fila de tarefas (task queue) é um padrão fundamental para organizar o processamento assíncrono de dados. Em C# (.NET), esse padrão é implementado através de vários mecanismos poderosos que permitem aos desenvolvedores gerenciar eficientemente os fluxos de execução e distribuir a carga.
A fila de tarefas resolve o problema clássico de producer-consumer (produtor-consumidor), onde uma parte do código cria tarefas e a outra as processa. Neste artigo, vamos explorar as principais formas de implementar filas de tarefas em C#: desde coleções simples até canais modernos (Channel).
Principais abordagens para implementar filas de tarefas
1. ConcurrentQueue — Fila thread-safe
A implementação mais simples e de baixo nível é ConcurrentQueue<T> do namespace System.Collections.Concurrent. Ela fornece os métodos thread-safe Enqueue e 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(); } } }}Vantagens:
- Simplicidade de uso
- Baixo consumo de memória
- Thread-safe sem bloqueios
Desvantagens:
- Espera ativa (busy waiting) no loop
- Ausência de sinalização sobre novas tarefas
- Dificuldade para parar
2. BlockingCollection — Fila com bloqueio
BlockingCollection<T> resolve o problema da espera ativa. Ela fornece os métodos Add e Take, que bloqueiam a thread até que os dados estejam disponíveis.
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 principais:
- Bloqueio automático da thread quando não há tarefas
- Suporte a cancelamento via
CancellationToken - Possibilidade de limitar a capacidade da fila
- Finalização segura através de
CompleteAdding
Abordagem moderna: Channel<T>
A partir do .NET Core 3.0 e .NET 5, o C# introduziu System.Threading.Channels.Channel<T> — esta é a implementação mais performática e flexível de fila de tarefas. Ela suporta operações assíncronas, cancelamento e limitação de throughput.
```