In C#, there are many classes that we can use to represent a group of objects that we may manipulate concurrently using multiple threads. One such class is the ConcurrentBag<T>.

In this article, we will learn how to add, access, and remove elements from a ConcurrentBag in C#. Then, we will discuss the advantages and disadvantages of this collection type.

To download the source code for this article, you can visit our GitHub repository.

Let’s begin.

What Is a ConcurrentBag in C#?

The ConcurrentBag<T> is a generic collection type in the System.Collection.Generic namespace that represents an unordered collection of objects. It implements the IProducerConsumerCollection<T> interface.

It is a thread-safe collection that is suitable for scenarios where we want to add and remove items concurrently using multiple threads. When we use this collection type, the order in which we add items to our collection, may not be the same as when we retrieve them. Additionally, we cannot access items in a ConcurrentBag through an indexer.

How to Create a ConcurrentBag in C#

Now, let’s look at the various ways in which we can create a ConcurrentBag in C#.

Creating an Empty ConcurrentBag in C#

When we create an object of the ConcurrentBag<T> type, we have to state the data type of the items it would store. For example, let’s create a ConcurrentBag that will contain integer items:

ConcurrentBag<int> myNumbers = new();

Here, we declare an empty ConcurrentBag that we can use to store integer values.

Creating a ConcurrentBag With Initial Items

Also, we can create an instance of the ConcurrentBag<T> class by specifying the initial values that it should contain:

var myConcurrentBag = new ConcurrentBag<int>() { 2, 4, 6, 8, 10 };

We initialize a ConcurrentBag that doesn’t have a specified capacity. Its size can increase or decrease dynamically depending on the operations that we perform on it. Also, we can iterate through this collection using a foreach loop.

Creating a ConcurrentBag by Passing Another Collection as an Argument

Another way we can create a ConcurrentBag<T> object is to pass any collection that implements the IEnumerable<T> interface to the constructor:

var myList = new List<int>() { 1, 2, 3, 4, 5, 6, 7 };
var myConcurrentBag = new ConcurrentBag<int>(myList);

It is important to note that the data type of the collection we pass in, must be the same as the type of ConcurrentBag.

Working With a ConcurrentBag in a Multithreaded Environment

In this section, let’s dive deeper and see how we can ensure thread safety in a multithreaded environment by using the ConcurrentBag<T> collection class. To start with, we will define a ConcurrentBag object, and then implement methods to manipulate its values from multiple threads. 

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Adding Elements to a ConcurrentBag Concurrently

First, let’s implement a method that declares a ConcurrentBag, and then concurrently adds elements to it:

public static ConcurrentBag<int> CreateAndAddToConcurrentBagConcurrently()
{
    ConcurrentBag<int> numbersBag = new();

    Parallel.For(0, 1000, i =>
    {
        numbersBag.Add(i);
    });

    return numbersBag;
}

First, we declare a ConcurrentBag<int> object. After that, we invoke the Parallel.For method to add items to our bag. We can use this method to execute a loop in parallel, with each iteration running on a separate thread.

The loop iterates from 0 to 1000 and adds each value to our ConcurrentBag. This allows us to add elements concurrently, without explicit synchronization (using the lock statement).

To get the number of elements in the returned collection, we can invoke the built-in Count property:

var myConcurrentBagCount = myConcurrentBag.Count;

Remove Elements From a ConcurrentBag

When we want to remove an item from a ConcurrentBag, we use the TryTake method. The TryTake method removes an item from the bag and returns it to the caller. If the collection is empty, it returns false:

public static ArrayList RemoveFromConcurrentBag(ConcurrentBag<int> numbersBag)
{
    var result = new ArrayList();
    if (numbersBag.TryTake(out int number))
    {
        result.Add(number);
    }

    return result;
}

In this method, we pass the bag as an argument. Of course, we want to remove an element from that bag. So, in the TryTake method, we pass an out parameter that will reference the returned value. Finally, we remove and return any number found in the collection.

We see that the CollectionBag<T> type is non-deterministic. This implies the order of removing items from the collection, may not be the same as the order of adding items.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Now, let’s see what happens when we try to remove items from a ConcurrentBag concurrently:

public static ArrayList RemoveFromConcurrentBagConcurrently(ConcurrentBag<int> bag)
{
    var numbersList = new ArrayList();
    Parallel.For(0, 20, i =>
    {
        if (bag.TryTake(out int number))
        {
            Console.WriteLine($"Thread {Environment.CurrentManagedThreadId} took item: {number}");
            numbersList.Add(number);
        }
    });

    return numbersList;
}

Again, we use the Parallel.For method to execute a loop concurrently, with each iteration running on a separate thread.

The loop iterates twenty times and tries to remove an item from our collection using the TryTake method. In each iteration, when we remove an item from the ConcurrentBag, we print it to the console along with the Id of the thread that removed it. Then, the item is added to our ArrayList.

Finally, we populate the list with the returned values from our ConcurrentBag.

It is important to note that the order in which we remove items from the ConcurrentBag may not be the same as the order in which we print them on the console because of the concurrent behavior of the operation.

That said if we want to check if a ConcurrentBag object is empty, we can use the IsEmpty property:

var isMyConcurrentBagEmpty = myConcurrentBag.IsEmpty;

Access an Element in a ConcurrentBag

Next up, let’s see how we can access an element in a ConcurrentBag. We can do this using the TryPeek method. When we invoke this method, it returns an item from the collection but does not remove it:

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<
public static ArrayList AccessItemFromAConcurrentBag(ConcurrentBag<int> bag)
{
    var result = new ArrayList();

    if (bag.TryPeek(out int number))
    {
        result.Add(number);
    }

    return result;
}

The TryPeek method retrieves an object from the bag without modifying the content of the bag and returns it to the caller through the number variable.

Now, if the bag contains an item and the TryPeek method returns true, we add the value of that item (stored in the number variable) to our ArrayList. Then, we return the list to the caller.

Finally, let’s see how to concurrently access an item from multiple threads, using the TryPeek method:

public static void AccessItemFromAConcurrentBagConcurrently(ConcurrentBag<int> bag)
{
    Parallel.For(0, 50, i =>
    {
        if (bag.TryPeek(out int number))
        {
            Console.WriteLine("Thread {0} peeked item: {1}", Environment.CurrentManagedThreadId, number);
        }
    });
}

Here, we invoke the Parallel.For method to execute a loop concurrently, with each iteration running on a separate thread.

Finally, the loop iterates fifty times, and each iteration tries to access an object from our collection using the TryPeek method. If the bag has an item, we print it on the console along with the ID of the thread that we use to access it.

Other Methods of the ConcurrentBag Class

Now, let’s discuss some of the most commonly used methods of the ConcurrentBag<T> class.

ToArray

The ToArray method copies all the elements of a ConcurrentBag into a new array:

public static int[] ConcurrentBagToArrayMethod(ConcurrentBag<int> bag)
{
    var myArray = new int[bag.Count];
    myArray = bag.ToArray();

    return myArray;
}

Here, the ToArray method populates myArray with items from myConcurrentBag. We should note that the order of the items in the array, may not be the same as the order in which we added them to the bag.

CopyTo

We use this method to copy the items in a ConcurrentBag to an existing one-dimensional array, starting at a specified index:

public static int[] ConcurrentBagCopyToMethod(ConcurrentBag<int> bag)
{
    var someArray = new int[bag.Count];
    bag.CopyTo(someArray, 0);

    return someArray;
}

Here, we populate the array with elements from the bag, starting at the index, 0. Again, the order of the items in the array, may not be the same as the order of the items when we inserted them into the bag.

Clear

This is a thread-safe method that we can invoke when we want to remove all items from a ConcurrentBag:

public static void ConcurrentBagClearMethod(ConcurrentBag<int> bag)
{
    bag.Clear();

    Console.WriteLine($"My concurrent bag contains {bag.Count} item."); // My concurrent bag contains 0 item.
}

As we can see, the bag is now empty.

Visit here, to see all the methods from the ConcurrentBag<T> class.

When to Use a ConcurrentBag in C#

We should use a ConcurrentBag when we have multiple threads that need to add or remove items from a collection concurrently. This ensures that our collection remains in a consistent state.

Also, we can use this collection type when we want to process grouped objects concurrently and don’t need to preserve the order of the objects.

For example, the ConcurrentBag type is suitable for situations where we want to process a group of objects by a set of worker threads. When we use this collection class, the worker threads can concurrently retrieve items from the collection and process them. Ensuring that we do not worry about synchronization or race conditions.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

That said, we must remember that items in a ConcurrentBag are unordered. Therefore, if we need to preserve the order of items, we should consider using a different concurrent collection type, like the ConcurrentQueue or the ConcurrentStack.

Benefits of a ConcurrentBag in C#

When we use the ConcurrentBag collection type, we can be sure that our application is thread-safe. This is so because the ConcurrentBag was designed to be used concurrently by multiple threads, and it provides thread safety for adding and removing items.

Also, using the ConcurrentBag can enhance our program’s performance because it uses a lock-free mechanism to support add and remove operations. This can result in better performance when compared with other concurrent collection types that use the lock construct.

Finally, because of its unordered nature and ability to increase or decrease its size dynamically, the ConcurrentBag class offers more flexibility. This allows us to store, remove and retrieve items in any order.

Drawbacks of a ConcurrentBag in C#

To start with, due to the unordered nature of the ConcurrentBag class, we cannot use it to represent a collection of objects where order is important to us. 

Also, if we use a ConcurrentBag to store a sequence of items that need to be processed in a specific order, the unordered nature of the bag can make it difficult to ensure that the items are processed in the correct order.

Finally, when we use a ConcurrentBag, we cannot use indexers, iterators, or other methods to access or modify the items in the bag in a specific order. This is because the ConcurrentBag<T> type does not provide many ways to access or modify the items in the bag.

Conclusion

In this article, we have seen that the ConcurrentBag is an efficient collection type in C# for storing and retrieving items concurrently from multiple threads. However, it does not guarantee the order in which we add and remove items from the collection. Finally, when we are to choose a collection type for concurrent programming in C#, we should consider the specific requirements of our application and select the most suitable collection.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<