Thread-Safe collections in C#

Thread-safe collections in C# are specialized data structures designed for concurrent programming. The System.Collections.Concurrent namespace, introduced in .NET Framework 4, provides collections that allow multiple threads to safely add, remove, and access items without external synchronization.

These collections use lightweight synchronization mechanisms like SpinLock and SpinWait to achieve thread safety while maintaining high performance and scalability.

Thread-Safe Collections Overview Thread A Thread B Thread C Concurrent Collection Multiple threads can safely access without locks

Types of Concurrent Collections

Type Description Use Case
ConcurrentDictionary<TKey,TValue> Thread-safe implementation of a dictionary of key-value pairs Shared caching, configuration data
ConcurrentQueue<T> Thread-safe FIFO (first-in, first-out) queue Producer-consumer scenarios
ConcurrentStack<T> Thread-safe LIFO (last-in, first-out) stack Task scheduling, undo operations
ConcurrentBag<T> Thread-safe unordered collection of elements Parallel processing with no ordering requirement
BlockingCollection<T> Provides bounding and blocking functionality Producer-consumer with flow control

Using ConcurrentDictionary

ConcurrentDictionary provides thread-safe operations for key-value pairs −

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

class Program {
   public static void Main() {
      ConcurrentDictionary<string, int> dict = new ConcurrentDictionary<string, int>();
      
      // Add items safely from multiple threads
      Parallel.For(0, 5, i => {
         string key = "Key" + i;
         dict.TryAdd(key, i * 10);
         Console.WriteLine($"Added {key}: {i * 10}");
      });
      
      // Update existing value atomically
      dict.AddOrUpdate("Key1", 100, (key, oldValue) => oldValue + 50);
      
      // Display all items
      foreach (var item in dict) {
         Console.WriteLine($"{item.Key}: {item.Value}");
      }
   }
}

The output of the above code is −

Added Key0: 0
Added Key1: 10
Added Key2: 20
Added Key3: 30
Added Key4: 40
Key0: 0
Key1: 60
Key2: 20
Key3: 30
Key4: 40

Using ConcurrentQueue and ConcurrentStack

These collections provide thread-safe FIFO and LIFO operations respectively −

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

class Program {
   public static void Main() {
      ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
      ConcurrentStack<int> stack = new ConcurrentStack<int>();
      
      // Add items to both collections
      Parallel.For(1, 6, i => {
         queue.Enqueue(i);
         stack.Push(i);
      });
      
      Console.WriteLine("Queue (FIFO):");
      while (queue.TryDequeue(out int queueItem)) {
         Console.WriteLine($"Dequeued: {queueItem}");
      }
      
      Console.WriteLine("\nStack (LIFO):");
      while (stack.TryPop(out int stackItem)) {
         Console.WriteLine($"Popped: {stackItem}");
      }
   }
}

The output of the above code is −

Queue (FIFO):
Dequeued: 1
Dequeued: 2
Dequeued: 3
Dequeued: 4
Dequeued: 5

Stack (LIFO):
Popped: 5
Popped: 4
Popped: 3
Popped: 2
Popped: 1

Using BlockingCollection

BlockingCollection provides producer-consumer functionality with blocking operations −

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

class Program {
   public static void Main() {
      BlockingCollection<int> collection = new BlockingCollection<int>(5); // bounded capacity
      
      // Producer task
      Task producer = Task.Run(() => {
         for (int i = 1; i <= 10; i++) {
            collection.Add(i);
            Console.WriteLine($"Produced: {i}");
            Thread.Sleep(100);
         }
         collection.CompleteAdding();
      });
      
      // Consumer task
      Task consumer = Task.Run(() => {
         foreach (int item in collection.GetConsumingEnumerable()) {
            Console.WriteLine($"Consumed: {item}");
            Thread.Sleep(150);
         }
      });
      
      Task.WaitAll(producer, consumer);
      Console.WriteLine("Production and consumption completed.");
   }
}

The output of the above code is −

Produced: 1
Consumed: 1
Produced: 2
Produced: 3
Consumed: 2
Produced: 4
Produced: 5
Consumed: 3
Produced: 6
Consumed: 4
Produced: 7
Consumed: 5
Produced: 8
Produced: 9
Consumed: 6
Produced: 10
Consumed: 7
Consumed: 8
Consumed: 9
Consumed: 10
Production and consumption completed.

Conclusion

Thread-safe collections in C# eliminate the need for manual synchronization when multiple threads access shared data. They provide better performance than traditional collections with locks and are essential for building scalable concurrent applications in .NET.

Updated on: 2026-03-17T07:04:35+05:30

910 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements