Thread safe collections in .NET: ConcurrentStack
January 14, 2014 Leave a comment
Concurrent collections in .NET work very much like their single-thread counterparts with the difference that they are thread safe. These collections can be used in scenarios where you need to share a collection between Tasks. They are typed and use a lightweight synchronisation mechanism to ensure that they are safe and fast to use in parallel programming.
Concurrent stacks
If you don’t know what Stacks are then you can read about them here. The Stack of T generic collection has a thread-safe counterpart called ConcurrentStack. Important methods:
- Push(T element): adds an item of type T to the collection
- PushRange(T[] elements) and PushRange(T[] elements, int, int): same as Push but is used for adding an array of items to the collection
- TryPeek(out T): tries to retrieve the next element from the collection without removing it. The value is set to the out parameter if the method succeeds. Otherwise it returns false.
- TryPop(out T): tries to get the first element. It removes the item from the collection and sets the out parameter to the retrieved element. Otherwise the method returns false
- TryPopRange(out T[] elements) and TryPopRange(out T[], int, int): same as TryPop but is used for arrays
The ‘try’ bit in the method names imply that your code needs to prepare for the event where the element could not be retrieved. If multiple threads retrieve elements from the same stack you cannot be sure what’s in there when a specific thread tries to read from it.
Example
Declare and fill a concurrent stack:
ConcurrentStack<int> concurrentStack = new ConcurrentStack<int>();
for (int i = 0; i < 5000; i++)
{
concurrentStack.Push(i);
}
Next we’ll try to pop every item from the stack. The stack will be accessed by several tasks at the same time. The counter variable – which is also shared – will be used to check if all items have been retrieved.
int counter = 0;
Task[] stackTasks = new Task[10];
for (int i = 0; i < stackTasks.Length; i++)
{
stackTasks[i] = Task.Factory.StartNew(() =>
{
while (concurrentStack.Count > 0)
{
int currentElement;
bool success = concurrentStack.TryPop(out currentElement);
if (success)
{
Interlocked.Increment(ref counter);
}
}
});
}
The while loop will ensure that we’ll try to pop the items as long as there’s something left in the collection.
Wait for the tasks and print the number of items processed – the counter should have the same value as the number of items in the stack:
Task.WaitAll(stackTasks);
Console.WriteLine("Counter: {0}", counter);
View the list of posts on the Task Parallel Library here.