A concurrent collection supports correct access by multiple threads without requiring callers to place a lock around every collection operation. Regular generic collections permit simultaneous reads, but concurrent writes—or reads during writes—can produce incorrect results or corruption. section 13.0 section 13.5
The System.Collections.Concurrent types combine commonly related operations and use fine-grained locking or lockless techniques. Their different interfaces matter: checking for a value and adding it must be treated as one race-free operation rather than as unrelated calls. section 13.1 section 13.2
Choose the collection according to access pattern:
- Use ConcurrentDictionary<TKey,TValue> for dictionary access, ConcurrentQueue<T> for queue access, and ConcurrentStack<T> for stack access. section 13.5
- Use ConcurrentBag<T> when ordering does not matter and the same threads both read and write items. section 13.5
- Use BlockingCollection<T> for producer–consumer workflows or bounded capacity; it blocks, so use it carefully with asynchronous code. section 13.5
A cache initialization race
A naïve cache checks TryGetValue, creates an item when absent, and adds it. Two threads can create the same item, then one can fail when the other adds first. A single lock prevents the race but may hold the lock during initialization; releasing it requires rechecking the dictionary and selecting the item already stored by another thread. A concurrent dictionary provides operations designed for this class of race. section 13.1 section 13.2
Do not choose a concurrent collection merely because multiple threads exist. If a lock already protects both the collection and surrounding application logic, Dictionary<TKey,TValue>, Queue<T>, or Stack<T> may be simpler and faster. If consumers tolerate stale data, immutable collections may be a better fit; they create modified collections while sharing internal data. section 13.2.7 section 13.3
Listing 13.1 Simple, non–thread-safe cache
if(!dictionary.TryGetValue(itemId, out var item))
{
item = CreateAndInitializeItem(itemId);
dictionary.Add(itemId,item);
}
Compare the naïve cache with locked and rechecked variants to see why concurrent collection operations need race-aware interfaces. section 13.1
