C# ConcurrentStack

Introduction

The ConcurrentStack<T> class in C# is part of the System.Collections.Concurrent namespace and represents a thread-safe, last-in, first-out (LIFO) collection of objects. It is particularly useful in scenarios where multiple threads are adding and removing items from a collection simultaneously, and you need a stack-like behavior. The ConcurrentStack<T> is designed to be thread-safe and optimized for high concurrency.

Key Features of ConcurrentStack

  • Thread-Safe: Supports concurrent push and pop operations.
  • LIFO Order: Maintains elements in a last-in, first-out order.
  • High Throughput: Optimized for scenarios with high contention.
  • No Locking: Uses lightweight synchronization mechanisms to avoid locks.

Creating a ConcurrentStack

Declaration and Initialization

You can declare and initialize a ConcurrentStack<T> in several ways:

Example

using System;
using System.Collections.Concurrent;

namespace ConcurrentStackExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Creating an empty ConcurrentStack
            ConcurrentStack<int> stack = new ConcurrentStack<int>();

            // Adding elements to the ConcurrentStack
            stack.Push(1);
            stack.Push(2);
            stack.Push(3);

            // Displaying elements
            Console.WriteLine("Elements in ConcurrentStack:");
            foreach (int item in stack)
            {
                Console.WriteLine(item);
            }
        }
    }
}

Output

Elements in ConcurrentStack:
3
2
1

Common Operations on ConcurrentStack

Adding Elements

  • Push: Adds an element to the top of the ConcurrentStack.
stack.Push(4);
  • PushRange: Adds multiple elements to the ConcurrentStack.
stack.PushRange(new int[] { 5, 6, 7 });

Removing Elements

  • TryPop: Attempts to remove and return an object from the top of the ConcurrentStack. Returns true if an object was removed successfully; otherwise, false.
bool success = stack.TryPop(out int item);
if (success)
{
    Console.WriteLine($"Popped: {item}");
}
else
{
    Console.WriteLine("No items to pop.");
}
  • TryPopRange: Attempts to pop and return multiple objects from the ConcurrentStack. Returns the number of objects successfully popped.
int[] items = new int[3];
int count = stack.TryPopRange(items);
Console.WriteLine($"Popped {count} items.");

Peeking at Elements

  • TryPeek: Attempts to return an object from the top of the ConcurrentStack without removing it. Returns true if an object was returned successfully; otherwise, false.
bool success = stack.TryPeek(out int item);
if (success)
{
    Console.WriteLine($"Peeked: {item}");
}
else
{
    Console.WriteLine("No items to peek.");
}

Checking the Collection Size

  • Count: Gets the number of elements in the ConcurrentStack.
int count = stack.Count;
Console.WriteLine($"Count: {count}");

Iterating Through a ConcurrentStack

You can iterate through a ConcurrentStack using a foreach loop. However, note that the collection is thread-safe for concurrent modifications, and the snapshot taken by foreach might not reflect all concurrent changes.

Example

foreach (int item in stack)
{
    Console.WriteLine(item);
}

Practical Example

Let’s create a practical example where we use a ConcurrentStack<T> to manage a collection of tasks executed by multiple threads, allowing them to add completed tasks to the stack.

Example

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace TaskCompletionExample
{
    class Program
    {
        static void Main(string[] args)
        {
            ConcurrentStack<string> completedTasks = new ConcurrentStack<string>();

            // Creating and starting multiple tasks
            Task[] tasks = new Task[5];
            for (int i = 0; i < tasks.Length; i++)
            {
                int taskId = i;
                tasks[i] = Task.Run(() => CompleteTask(completedTasks, taskId));
            }

            // Waiting for all tasks to complete
            Task.WaitAll(tasks);

            // Displaying completed tasks
            Console.WriteLine("Completed Tasks:");
            foreach (string task in completedTasks)
            {
                Console.WriteLine(task);
            }
        }

        static void CompleteTask(ConcurrentStack<string> completedTasks, int taskId)
        {
            // Simulate some work
            Task.Delay(1000).Wait();
            string taskName = $"Task {taskId}";
            completedTasks.Push(taskName);
            Console.WriteLine($"{taskName} completed.");
        }
    }
}

Output

Task 0 completed.
Task 1 completed.
Task 2 completed.
Task 3 completed.
Task 4 completed.
Completed Tasks:
Task 4
Task 3
Task 2
Task 1
Task 0

Conclusion

The ConcurrentStack<T> class in C# provides a thread-safe, last-in, first-out (LIFO) collection of objects that is optimized for high concurrency. It supports concurrent push and pop operations without locking, making it ideal for scenarios where multiple threads need to interact with a shared stack. Understanding how to use ConcurrentStack<T> effectively can help you manage collections of data in multi-threaded applications.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top