Skip to main content

ConcurrentBag: Coleção Concorrente Não Ordenada para Processamento Paralelo | CSharp

  • ConcurrentBag: Coleção Concorrente Não Ordenada para Processamento Paralelo | CSharp

ConcurrentBag in C# - Code Maze: 🔗 Clique aqui

Em aplicações multithreaded, a escolha correta das estruturas de dados pode ser determinante para a escalabilidade e robustez do sistema. O ConcurrentBag<T> é uma das coleções disponíveis na biblioteca base do .NET que oferece acesso concorrente seguro com um custo relativamente baixo de sincronização. Neste artigo, vamos entender quando e por que usar o ConcurrentBag, suas principais características, vantagens, limitações e cenários adequados de uso.


O que é o ConcurrentBag?

O ConcurrentBag<T> é uma coleção genérica que pertence ao namespace System.Collections.Concurrent. Seu principal propósito é armazenar itens de forma não ordenada, permitindo que múltiplas threads adicionem e removam elementos simultaneamente, sem a necessidade de implementar mecanismos manuais de sincronização.

Essa estrutura implementa a interface IProducerConsumerCollection<T>, o que significa que ela é adequada para padrões de produtor/consumidor, embora não garanta nenhuma ordenação na inserção ou retirada dos elementos.


Características principais

  • Thread-safe: todas as operações são seguras em ambientes concorrentes.
  • Sem ordenação: a ordem dos elementos não é garantida, nem na adição, nem na remoção.
  • Acesso indireto: não há indexação, ou seja, não é possível acessar elementos por posição.
  • Desempenho otimizado: utiliza mecanismos lock-free internos, o que pode levar a uma performance superior em determinados cenários.

Como usar o ConcurrentBag

1. Criação

ConcurrentBag<int> bag = new();

Ou com valores iniciais:

var bag = new ConcurrentBag<int> { 1, 2, 3, 4 };

Ou ainda, a partir de outra coleção:

var lista = new List<int> { 1, 2, 3 };
var bag = new ConcurrentBag<int>(lista);

2. Adição de elementos (Add)

bag.Add(42);

3. Remoção de elementos (TryTake)

if (bag.TryTake(out int item))
{
// item removido com sucesso
}

4. Visualização sem remoção (TryPeek)

if (bag.TryPeek(out int item))
{
// item lido, mas não removido
}

Concorrência na prática

A principal vantagem do ConcurrentBag se manifesta em processamentos paralelos, onde vários produtores e consumidores trabalham simultaneamente.

Exemplo: Adição concorrente

ConcurrentBag<int> bag = new();

Parallel.For(0, 1000, i => bag.Add(i));

Exemplo: Remoção concorrente

Parallel.For(0, 100, i =>
{
if (bag.TryTake(out int numero))
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} removeu {numero}");
}
});

Esses exemplos mostram como podemos manipular a coleção de forma paralela sem nos preocupar com locks explícitos ou condições de corrida.


Métodos úteis

  • ToArray() – retorna os itens como um array.
  • CopyTo(array, index) – copia os itens para um array existente.
  • Clear() – remove todos os elementos da coleção.
  • IsEmpty – verifica se a coleção está vazia.
  • Count – retorna o número de elementos (pode ser impreciso em concorrência extrema).

Quando usar ConcurrentBag

Cenários apropriados:

  • Quando múltiplas threads precisam acessar (ler/gravar) uma coleção simultaneamente.
  • Quando a ordem dos elementos não importa.
  • Quando se busca um bom compromisso entre performance e simplicidade.

Evite usar se:

  • A ordem dos elementos for relevante (use ConcurrentQueue ou ConcurrentStack).
  • For necessário acesso direto por índice.
  • Precisar de controle mais fino sobre iteração ou ordenação.

Vantagens

  • Implementação pronta e otimizada da .NET.
  • Permite processamento paralelo eficiente sem complexidade adicional.
  • Desempenho superior em cenários com alta taxa de inserção/remoção.

Desvantagens

  • Não garante ordem dos dados.
  • Não suporta indexadores.
  • Pouco adequada para casos onde os itens precisam ser processados em sequência.

Conclusão

O ConcurrentBag<T> é uma estrutura poderosa para aplicações que envolvem múltiplas threads interagindo com uma coleção de dados de maneira dinâmica e sem a necessidade de preservação de ordem. Seu uso pode simplificar a implementação e melhorar o desempenho de sistemas multithreaded.

Entretanto, como toda abstração, deve ser utilizada com entendimento claro de suas limitações. A escolha da estrutura de dados correta é fundamental para manter a coerência, a performance e a previsibilidade do comportamento da aplicação.

Se o seu cenário se encaixa nesse perfil – múltiplos acessos concorrentes e desprezo pela ordem – o ConcurrentBag é uma escolha certeira.