liveBook · Manning

Concurrent collection

As explained in C# Concurrency: Asynchronous and multithreaded programming

A concurrent collection is a collection designed to support correct access by multiple threads without requiring callers to provide all synchronization.

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

How C# Concurrency: Asynchronous and multithreaded programming covers it

How deep it goes
Explains races, collection interfaces, locking strategies, and selection trade-offs, but does not detail each implementation internally.
Examples
Uses a multithreaded cache, ConcurrentDictionary<TKey,TValue>, ConcurrentBag<T>, and BlockingCollection<T> as concrete examples.
What it assumes
Assumes C# collections and multiple-thread access, while teaching why ordinary collection operations and compound checks race.
What you can do after
Evaluate and choose .NET concurrent collection types for races, unordered access, and producer–consumer pipelines.
How it connects
Connects thread safety with locking, producer–consumer workflows, asynchronous code, and immutable collections.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C# Concurrency: Asynchronous and multithreaded programming. The section links point to the parts of the book it draws on.