Block Collection
- Block Collection
O BlockingCollection<T> em C# é uma classe do namespace System.Collections.Concurrent, usada para gerenciar uma coleção de itens que oferece suporte à concorrência (acesso de múltiplas threads) e ao bloqueio. Ele é frequentemente utilizado em cenários de produção e consumo de dados (produtor-consumidor), onde uma ou mais threads produzem dados e uma ou mais threads consomem esses dados.
Principais Características do BlockingCollection<T>
- Bloqueio: Ele bloqueia automaticamente a thread que tenta adicionar ou remover itens quando a coleção atinge uma capacidade específica (opcional) ou quando está vazia, respectivamente.
- Controle de Capacidade: Você pode definir um limite de capacidade para a coleção, útil para evitar o uso excessivo de memória quando há muitos itens na coleção.
- Concorrência: Como ele é seguro para acesso concorrente, várias threads podem adicionar e remover itens simultaneamente, sem a necessidade de gerenciar locks explicitamente.
Cenário Típico de Uso: Padrão Produtor-Consumidor
No padrão produtor-consumidor, uma thread (ou várias) insere itens na coleção enquanto outra thread (ou várias) retira itens para processamento. O BlockingCollection permite gerenciar isso com bloqueios automáticos, simplificando a coordenação entre threads.
Criando e Usando o BlockingCollection
Aqui está um exemplo básico para ilustrar o uso do BlockingCollection em um cenário produtor-consumidor:
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
// Cria uma BlockingCollection com uma capacidade máxima de 5 itens
using (var collection = new BlockingCollection<int>(5))
{
// Tarefa Produtora: Adiciona itens na coleção
Task producer = Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Produzindo {i}");
collection.Add(i); // Adiciona o item na coleção (pode bloquear se a capacidade for atingida)
Thread.Sleep(500); // Simula o tempo de produção
}
collection.CompleteAdding(); // Sinaliza que não haverá mais itens
});
// Tarefa Consumidora: Consome itens da coleção
Task consumer = Task.Run(() =>
{
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"Consumindo {item}");
Thread.Sleep(1000); // Simula o tempo de processamento
}
});
// Aguarda as tarefas completarem
Task.WaitAll(producer, consumer);
}
}
}
Explicação do Código
-
Criação da
BlockingCollection:- Definimos a coleção com uma capacidade de 5 itens. Isso significa que, se houver mais de 5 itens, o produtor será bloqueado até que o consumidor retire algum item.
-
Tarefa Produtora:
- A tarefa produtora adiciona itens à coleção com o método
Add. Se a coleção atingir sua capacidade (5 itens neste caso), a thread produtora será bloqueada até que haja espaço disponível. - Após adicionar todos os itens (de 0 a 9), chamamos
CompleteAdding(), que indica que não haverá mais itens a serem adicionados, facilitando a sinalização para o consumidor de que não precisa mais esperar.
- A tarefa produtora adiciona itens à coleção com o método
-
Tarefa Consumidora:
- A tarefa consumidora consome itens da coleção usando um
foreachno métodoGetConsumingEnumerable(). Esse método bloqueia a iteração até que um novo item esteja disponível ou até que a coleção seja sinalizada como "completa" (sem novos itens para adicionar). - Cada item é processado com um
Thread.Sleep(1000), que simula um tempo de processamento mais demorado.
- A tarefa consumidora consome itens da coleção usando um
Métodos Principais do BlockingCollection
Add(item): Adiciona um item à coleção. Se a capacidade for atingida, a chamada será bloqueada até que haja espaço.Take(): Remove e retorna um item da coleção. Se a coleção estiver vazia, a chamada será bloqueada até que haja algum item.CompleteAdding(): Indica que não serão adicionados mais itens. Os consumidores serão notificados ao processarem todos os itens restantes.GetConsumingEnumerable(): Retorna umIEnumerable<T>para iterar e consumir os itens. Esse método é útil para consumidores que querem processar os itens até que a coleção seja esvaziada e sinalizada como completa.
Quando Usar o BlockingCollection
Use BlockingCollection em cenários onde você precisa:
- Implementar o padrão produtor-consumidor de forma eficiente e segura para threads.
- Gerenciar a troca de dados entre threads, permitindo que uma thread produza dados em uma taxa diferente daquela em que outras threads os consomem.
- Aplicar controle de capacidade a uma coleção compartilhada para limitar o número de itens (útil para evitar o consumo excessivo de recursos).
O BlockingCollection simplifica bastante a sincronização entre threads, especialmente em comparação com a necessidade de gerenciar locks, monitores ou outros mecanismos de sincronização manual.