PROWAREtech
.NET: ConcurrentDictionary - What is It and How to Use it?
See also ConcurrentBag class.
A ConcurrentDictionary
in C# is a thread-safe collection that stores key-value pairs, designed for scenarios where multiple threads need to simultaneously add, remove, or modify items. Unlike a regular Dictionary, it provides atomic operations that are safe for concurrent access without explicit locking, making it ideal for producer-consumer patterns or caching scenarios where one needs to ensure data consistency across threads.
A ConcurrentBag
, on the other hand, is an unordered collection of objects that allows duplicate values and is optimized for scenarios where the same thread is both adding and removing items. While both collections are thread-safe, ConcurrentBag
doesn't maintain key-value relationships and excels in situations where one doesn't need to track specific items but rather just need a thread-safe container for objects, like a pool of resources.
The main difference lies in their use cases: ConcurrentDictionary
is best when one needs to maintain unique keys and look up specific values quickly, while ConcurrentBag
is more suitable for scenarios where one's just collecting items and don't care about their order or uniqueness. ConcurrentDictionary
provides O(1) lookup performance, while ConcurrentBag
is optimized for scenarios where the same thread that added an item is likely to remove it.
Example Code
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
class Program
{
static void Main()
{
// Create a ConcurrentDictionary
ConcurrentDictionary<int, string> dict = new ConcurrentDictionary<int, string>();
// Add or update items in the ConcurrentDictionary in parallel
Parallel.For(0, 10, i =>
{
dict.AddOrUpdate(i, $"Value {i}", (key, oldValue) => $"Value {i}");
Console.WriteLine($"Added or updated key {i}");
});
// Read items from the ConcurrentDictionary
Parallel.For(0, 10, i =>
{
string result;
if (dict.TryGetValue(i, out result))
{
Console.WriteLine($"Key {i} has value {result}");
}
});
// Display contents of the ConcurrentDictionary
foreach (var kvp in dict)
{
Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
}
}
}
// Creating a cache for expensive operations
using System.Collections.Concurrent;
var cache = new ConcurrentDictionary<string, ExpensiveResult>();
// GetOrAdd - will only compute the value if the key doesn't exist
var result = cache.GetOrAdd("key1", key => {
// This expensive operation only runs if key1 doesn't exist
return PerformExpensiveOperation(key);
});
// Thread-safe updates using AddOrUpdate
var userVisits = new ConcurrentDictionary<string, int>();
userVisits.AddOrUpdate(
"user123",
// Add value if key doesn't exist
key => 1,
// Update value if key exists
(key, oldValue) => oldValue + 1
);
// Safe removal with TryRemove
if (cache.TryRemove("key1", out var removedValue))
{
Console.WriteLine($"Successfully removed: {removedValue}");
}
// Atomic updates using GetOrAdd with a factory
var counter = new ConcurrentDictionary<string, int>();
Parallel.ForEach(items, item => {
counter.AddOrUpdate(
item.Category,
// Initial value if key doesn't exist
category => 1,
// Update function if key exists
(category, existingCount) => existingCount + 1
);
});
// Thread-safe initialization of complex objects
var connections = new ConcurrentDictionary<string, DatabaseConnection>();
var connection = connections.GetOrAdd("db1", serverName => {
return new DatabaseConnection(serverName, timeout: 30);
});