Introduction
The ConcurrentDictionary<TKey, TValue>
class in C# is part of the System.Collections.Concurrent
namespace and represents a thread-safe collection of key/value pairs. It is optimized for scenarios where multiple threads are reading from and writing to the dictionary concurrently. ConcurrentDictionary<TKey, TValue>
provides high throughput and thread-safe access to its elements without the need for external synchronization.
Key Features of ConcurrentDictionary<TKey, TValue>
- Thread-Safe: Supports concurrent read and write operations.
- High Throughput: Optimized for scenarios with high contention.
- Type Safety: Ensures type safety for keys and values.
- Lock-Free: Uses fine-grained locking and lock-free techniques to minimize contention.
Creating a ConcurrentDictionary
Declaration and Initialization
You can declare and initialize a ConcurrentDictionary<TKey, TValue>
in several ways:
Example
using System;
using System.Collections.Concurrent;
namespace ConcurrentDictionaryExample
{
class Program
{
static void Main(string[] args)
{
// Creating an empty ConcurrentDictionary
ConcurrentDictionary<int, string> employees = new ConcurrentDictionary<int, string>();
// Creating a ConcurrentDictionary with initial elements
var departments = new ConcurrentDictionary<int, string>
{
[1] = "Human Resources",
[2] = "Finance",
[3] = "Engineering"
};
// Adding elements to the ConcurrentDictionary
employees.TryAdd(101, "John Doe");
employees.TryAdd(102, "Jane Smith");
employees.TryAdd(103, "Sam Brown");
// Displaying elements
Console.WriteLine("Employees:");
foreach (var kvp in employees)
{
Console.WriteLine($"ID: {kvp.Key}, Name: {kvp.Value}");
}
}
}
}
Output
Employees:
ID: 101, Name: John Doe
ID: 102, Name: Jane Smith
ID: 103, Name: Sam Brown
Common Operations on ConcurrentDictionary
Adding and Updating Elements
- TryAdd: Adds a key/value pair to the
ConcurrentDictionary
if the key does not already exist.
bool added = employees.TryAdd(104, "Alice Johnson");
- AddOrUpdate: Adds a key/value pair if the key does not exist, or updates the value if the key already exists.
string updatedName = employees.AddOrUpdate(101, "John Smith", (key, oldValue) => "John Smith");
- GetOrAdd: Retrieves the value associated with the specified key, or adds a key/value pair if the key does not exist.
string name = employees.GetOrAdd(105, "Eve White");
Removing Elements
- TryRemove: Attempts to remove and return the value with the specified key from the
ConcurrentDictionary
. Returnstrue
if the key was found and removed; otherwise,false
.
bool removed = employees.TryRemove(102, out string removedName);
Accessing Elements
- Indexer: Accesses elements by their key.
string employeeName = employees[101];
- ContainsKey: Checks if the
ConcurrentDictionary
contains the specified key. Returnstrue
if the key is found; otherwise,false
.
bool hasEmployee = employees.ContainsKey(101); // true
Iterating Through a ConcurrentDictionary
You can iterate through a ConcurrentDictionary
using a foreach
loop. The collection is thread-safe for concurrent modifications, and the snapshot taken by foreach
might not reflect all concurrent changes.
Example
foreach (var kvp in employees)
{
Console.WriteLine($"ID: {kvp.Key}, Name: {kvp.Value}");
}
Checking the Dictionary Size
- Count: Gets the number of key/value pairs in the
ConcurrentDictionary
.
int count = employees.Count;
Console.WriteLine($"Count: {count}");
Practical Example
Let’s create a practical example where we use a ConcurrentDictionary<TKey, TValue>
to manage a collection of product inventories, allowing multiple threads to add, update, and remove products concurrently.
Example
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace ProductInventoryExample
{
class Program
{
static void Main(string[] args)
{
ConcurrentDictionary<int, int> productInventory = new ConcurrentDictionary<int, int>();
// Adding initial products
productInventory.TryAdd(1, 100);
productInventory.TryAdd(2, 150);
productInventory.TryAdd(3, 200);
// Creating and starting multiple tasks to update inventory
Task[] tasks = new Task[5];
for (int i = 0; i < tasks.Length; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => UpdateInventory(productInventory, taskId));
}
// Waiting for all tasks to complete
Task.WaitAll(tasks);
// Displaying final product inventory
Console.WriteLine("Product Inventory:");
foreach (var kvp in productInventory)
{
Console.WriteLine($"Product ID: {kvp.Key}, Quantity: {kvp.Value}");
}
}
static void UpdateInventory(ConcurrentDictionary<int, int> inventory, int taskId)
{
Random random = new Random();
for (int i = 0; i < 10; i++)
{
int productId = random.Next(1, 4);
int quantityChange = random.Next(-10, 11);
inventory.AddOrUpdate(productId, quantityChange, (key, oldValue) => oldValue + quantityChange);
Console.WriteLine($"Task {taskId} updated product {productId} by {quantityChange}");
Task.Delay(100).Wait(); // Simulate some work
}
}
}
}
Output
Task 0 updated product 3 by 5
Task 1 updated product 2 by -7
Task 2 updated product 1 by 3
...
Product Inventory:
Product ID: 1, Quantity: 97
Product ID: 2, Quantity: 145
Product ID: 3, Quantity: 210
Conclusion
The ConcurrentDictionary<TKey, TValue>
class in C# provides a thread-safe collection of key/value pairs that is optimized for high concurrency. It supports concurrent read and write operations without locking, making it ideal for scenarios where multiple threads need to interact with a shared dictionary. Understanding how to use ConcurrentDictionary<TKey, TValue>
effectively can help you manage collections of data in multi-threaded applications.