Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
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.
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.
