[C#] Concurrency cheat sheet
The .NET Framework provides a ridiculous number of solutions to deal with concurrency. You probably know the lock statement or the ManualResetEvent class, but you’ll see that there are many more options.
Life is always easier when you choose the appropriate tool, so you’d better know what’s available. This article gathers all the information in one place.
Table of content:
- ArrayList.Synchronizedmethod
- AutoResetEventclass
- Barrierclass
- BlockingCollectionclass
- CancellationTokenstruct
- ConcurrentBagclass
- ConcurrentDictionaryclass
- ConcurrentQueueclass
- ConcurrentStackclass
- CountdownEventclass
- EventWaitHandleclass
- Interlockedoperations
- lockstatement
- ManualResetEventclass
- ManualResetEventSlimclass
- MethodImplOptions.Synchronizedattribute
- Monitor.Enterand- .Exitmethods
- Monitor.Pulsemethod
- Monitor.PulseAllmethod
- Mutexclass
- ReaderWriterLockclass
- ReaderWriterLockSlimclass
- Semaphoreclass
- SemaphoreSlimclass
- SpinLockclass
- SpinWaitclass
- Thread.MemoryBarriermethod (and- Interlocked.MemoryBarrier)
- Thread.VolatileReadand- .VolatileWritemethods
- volatilekeyword
- Volatile.Readand- Writemethods
1. ArrayList.Synchronized
For those who still use ArrayList, there is a simple way to make it thread safe:
array = ArrayList.Synchronized(new ArrayList());
ArrayList.Synchronized() returns a thread-safe wrapper of the given array. Most operation, such as array.Add(), array[i] or array.Count, are thread safe.
Unfortunately, enumerating the ArrayList is not thread-safe. The recommended pattern is:
lock(array.SyncRoot)
{
    foreach (object item in array)
    {
        // ...
    }
}
| Pros | Easy to use | 
|---|---|
| Cons | Only works for ArrayList | 
| Since | .NET Framework 1.0 | 
| Alt. | ConcurrentBag, ConcurrentQueue, ConcurrentStack | 
| Links | MSDN: ArrayList.Synchronized Method (ArrayList) | 
2. AutoResetEvent
When signaled, a AutoResetEvent releases exactly one waiting thread. It’s commonly use to perform exclusive access. This is different from a ManualResetEvent which releases all waiting threads.
readonly AutoResetEvent autoResetEvent = new AutoResetEvent(true);
autoResetEvent.WaitOne();
// ...insert code here...
autoResetEvent.Set();
In this particular example, and AutoResetEvent behaves like a Semaphore with max set to 1.
| Pros | Resumes only one waiting thread | 
|---|---|
| Cons | Doesn't support named events | 
| Since | .NET Framework 1.0 | 
| Alt. | EventWaitHandle, lock, Monitor, Mutex, Semaphore | 
| Links | MSDN: AutoResetEvent Class MSDN: EventWaitHandle Class StackOverflow: ManualResetEvent vs AutoResetEvent StackOverflow: AutoResetEvent vs Semaphore | 
3. Barrier
Barrier class helps synchronizing parallel operations when an algorithm is composed of several phases. Threads will wait for each others so that they are always working on the same phase.
 while (someCondition)
 {
     // ...insert code here...
     barrier.SignalAndWait();
 }
| Pros | Very useful for parallel processing | 
|---|---|
| Cons | Only for parallel algorithm with multiple phases | 
| Since | .NET Framework 4 | 
| Alt. | CountdownEvent, Task.WaitAll(), Thread.Join(), Semaphore | 
| Links | MSDN: Barrier Class MSDN: Synchronize Concurrent Operations with a Barrier Wikipedia: Barrier (computer science) | 
4. BlockingCollection
ConcurrentBag, ConcurrentQueue and ConcurrentStack allows extracting values from the collection in a non-blocking manner. But what if you want to wait until the value is available?
BlockingCollection is a wrapper that adds this feature: when you call Take(), it will block until a value is available.
BlockingCollection also comes with another feature: you can decide to limit the size of the collection. Attempts to add values to a collection that has reached the maximum size, will block the thread until room is available.
If you don’t specify the underlying collection, a ConcurrentQueue is used.
collection = new BlockingCollection<T>();
collection = new BlockingCollection<T>(new ConcurrentBag<T>());
collection = new BlockingCollection<T>(new ConcurrentQueue<T>());
collection = new BlockingCollection<T>(new ConcurrentStack<T>());
collection.Add(value);
value = collection.Take();
| Pros | Works with various collections | 
|---|---|
| Cons | None | 
| Since | .NET Framework 4 | 
| Alt. | Semaphore, CountdownEvent | 
| Links | MSDN: BlockingCollection<T> Class | 
5. CancellationToken
CancellationToken is the preferred way to cancel an operation.
The master thread triggers the cancellation through a CancellationTokenSource.
The slave thread must periodically poll the state of the CancellationToken.
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
// Master thread
cancellationTokenSource.Cancel();
// Slave thread
while (!cancellationToken.IsCancellationRequested)
{
    // ... insert code here...
}
// Slave thread (other way to use it)
while (true)
{
	cancellationToken.ThrowIfCancellationRequested();
    // ... insert code here...
}
| Pros | Clear purpose: cancel | 
|---|---|
| Cons | None | 
| Since | .NET Framework 4 | 
| Alt. | ManualResetEvent | 
| Links | MSDN: CancellationToken Structure MSDN: CancellationTokenSource Class MSDN: Cancellation Tokens | 
6. ConcurrentBag
ConcurrentBag is a thread-safe unordered collection.
bag = new ConcurrentBag<T>();
bag.Add(value);
bag.TryPeek(out value);
bag.TryTake(out value);
| Pros | Non blocking read access | 
|---|---|
| Cons | Order is not preserved | 
| Since | .NET Framework 4 | 
| Alt. | ArrayList.Synchronized, ConcurrentQueue, ConcurrentStack | 
| Links | MSDN: ConcurrentBag<T> Class | 
7. ConcurrentDictionary
ConcurrentDictionary is a thread-safe Dictionary. Even though it explicitly implements IDictionary, its public API is quite different from a classic Dictionary.
dict = new ConcurrentDictionary<TKey,TValue>();
dict.TryAdd(key, value);
dict.TryGetValue(key, out value);
dict.TryRemove(key, out value);
dict.AddOrUpdate(key, value, (k, v) => value);
dict.AddOrUpdate(key, k => value, (k, v) => value);
dict.GetOrAdd(key, k => value);
| Pros | Non blocking read access | 
|---|---|
| Cons | Not as simple as you expect | 
| Since | .NET Framework 4 | 
| Alt. | lock, Monitor | 
| Links | MSDN: ConcurrentDictionary<TKey, TValue> Class | 
8. ConcurrentQueue
ConcurrentQueue is a thread-safe version of Queue.
queue = new ConcurrentQueue<T>();
queue.Enqueue(value);
queue.TryPeek(out value);
queue.TryDequeue(out value);
| Pros | Non blocking read access | 
|---|---|
| Cons | Interface differs from Queue | 
| Since | .NET Framework 4 | 
| Alt. | ConcurrentBag, ConcurrentStack, lock, Monitor | 
| Links | MSDN: ConcurrentQueue<T> Class | 
9. ConcurrentStack
ConcurrentStack is a thread-safe version of Stack.
stack = new ConcurrentStack<T>();
stack.Push(value);
stack.TryPeek(out value);
stack.TryPop(out value);
| Pros | Non blocking read access | 
|---|---|
| Cons | Interface differs from Stack | 
| Since | .NET Framework 4 | 
| Alt. | ConcurrentBag, ConcurrentQueue, lock, Monitor | 
| Links | MSDN: ConcurrentStack<T> Class | 
10. CountdownEvent
CountdownEvent is like a reverse semaphore: it get signaled when the count reaches zero. For instance, it can be used to wait several thread to complete some operation.
readonly CountdownEvent cde = new CountdownEvent(0);
“Main” thread:
cde.AddCount(2);
// start worker threads...
cde.Wait();
“Worker” threads:
// do the work...
cde.Signal()
| Pros | Good alternative to Semaphore | 
|---|---|
| Cons | Can't work across processes like Semaphore | 
| Since | .NET Framework 4 | 
| Alt. | Barrier, Semaphore, SemaphoreSlim | 
| Links | MSDN: CountdownEvent Class | 
11. EventWaitHandle
EventWaitHandle is a newer version of both AutoResetEvent and ManualResetEvent (the “reset” behavior is determined by the enum EventResetMode). It supports named event, so it can do signaling across different processes.
readonly EventWaitHandle eventWaitHandle = 
    new EventWaitHandle(false, EventResetMode.ManualReset);
// master thread
eventWaitHandle.WaitOne();
// slave threads
eventWaitHandle.Set();
| Pros | Supports named events (cross process) | 
|---|---|
| Cons | Slow (kernel mode) | 
| Since | .NET Framework 2.0 | 
| Alt. | AutoResetEvent, CancellationToken, ManualResetEvent, Monitor | 
| Links | MSDN: EventWaitHandle Class | 
12. Interlocked operations
The Interlocked class provides several static methods that implements atomic operations.
Add(), Increment() and Decrement() can be used for int and long:
Interlocked.Add(ref field, 42);		// field += 42;
Interlocked.Increment(ref field);   // field++;
Interlocked.Decrement(ref field);   // field--;
Exchange() and CompareExchange() can be used for int, long, float, 
double, IntPtr and any class (but not struct):
// var tmp = field;
// field = 42;
// return tmp;
Interlocked.Exchange(ref field, 42);
// var tmp = field;
// if (field == 21)
//    field = 42;    
// return tmp;
Interlocked.CompareExchange(ref field, 42, 21);
Finally, Read() allows to read a long in an atomic way.
| Pros | Non blocking | 
|---|---|
| Cons | Verbose | 
| Since | .NET Framework 1.0 | 
| Alt. | lock, Monitor, Volatile class, volatile keyword | 
| Links | MSDN: Interlocked Methods | 
13. Lock statement
The lock statement is the most natural way to implement mutual exclusion in C#.
 lock (syncObject)
 {
 	// use shared resource...
 }
| Pros | Lock is released if exception is thrown | 
|---|---|
| Cons | Has kwown pitfalls: lock a public object or a string | 
| Since | C# specifications 1.0 | 
| Alt. | Monitor, Mutex | 
| Links | MSDN: lock Statement (C# Reference) Coverity: Can I skip the lock when reading an integer? | 
14. ManualResetEvent
When signaled, a ManualResetEvent releases all waiting threads. This is different from a ManualResetEvent which releases exactly one waiting thread.
// master thread
manualResetEvent.WaitOne();
// slave threads
manualResetEvent.Set();
| Pros | Resumes all waiting threads | 
|---|---|
| Cons | Doesn't support named events | 
| Since | .NET Framework 1.0 | 
| Alt. | CancellationToken, EventWaitHandle, ManualResetEventSlim, SpinWait | 
| Links | MSDN: ManualResetEvent Class MSDN: ManualResetEvent and ManualResetEventSlim | 
15. ManualResetEventSlim
ManualResetEventSlim is a lightweight version of ManualResetEvent that provides better performance when the wait is short. It’s an hybrid construct: it first spin waits for a short time, then yields if still not signaled.
// master thread
manualResetEventSlim.Wait();
// slave threads
manualResetEventSlim.Set();
| Pros | Performance for short waits | 
|---|---|
| Cons | Not the same API as ManualResetEvent | 
| Since | .NET Framework 4.0 | 
| Alt. | CancellationToken, EventWaitHandle, ManualResetEvent, SpinWait | 
| Links | MSDN: ManualResetEventSlim Class MSDN: ManualResetEvent and ManualResetEventSlim | 
16. MethodImplOptions.Synchronized
The attribute MethodImplAttribute can be set on methods. If MethodImplOptions.Synchronized is specified, the method will be executed by only one thread at a time.
When applied to instance methods, it locks the instance, ie this.
When applied to static methods, it locks the Type.
Both case are nowadays considered as very bad practices.
[MethodImpl(MethodImplOptions.Synchronized)]
void SomeMethod()
{
	// use shared resource...
}
This is equivalent to
void SomeMethod()
{
	lock (this)
    {
    	// use shared resource...
    }
}
| Pros | Short code, easy to read | 
|---|---|
| Cons | Locks the instance instead of a private field. Considered as a bad practice. | 
| Since | .NET Framework 1.1 | 
| Alt. | lock statement, Monitor | 
| Links | MethodImplOptions Enumeration StackOverflow: What does MethodImplOptions.Synchronized do? StackOverflow: Manual locking vs Synchronized methods | 
17. Monitor.Enter/Exit
The static methods Monitor.Enter() and Monitor.Exit() allows to make the same thing as the lock statement.
Monitor.Enter(syncObject);
// use shared resource...
Monitor.Exit(syncObject);
In a real project, you would call Exit() in a finally block.
| Pros | More flexible that the lock statement | 
|---|---|
| Cons | Same pitfalls as the lock statement | 
| Since | .NET Framework 1.0 | 
| Alt. | AutoResetEvent, Mutex, lock statement, SpinLock | 
| Links | MSDN: Monitor.Enter Method MSDN: Monitor.Exit Method | 
18. Monitor.Pulse()
Monitor.Pulse() method allows to wake a waiting thread and transfer the lock ownership from one thread to another.
Thread “Producer”:
lock (syncObject)
{
	// produce some data...
    Monitor.Pulse(syncObject);
}
Thread “Consumer”:
lock (syncObject) 
{
	Monitor.Wait(syncObject);
    // consume the data...
}
| Pros | Lock is constantly held | 
|---|---|
| Cons | Very tricky | 
| Since | .NET Framework 1.0 | 
| Alt. | AutoResetEvent, ReaderWriterLock, ReaderWriterLockSlim | 
| Links | MSDN: Monitor.Pulse Method | 
19. Monitor.PulseAll()
As the name suggests, PulseAll() does the same thing as Pulse() but instead of waking the next waiter, it wakes all of them.
Whereas Pulse() is suited for a producer/consumer pattern where only one consumer can use the data, PulseAll() would be useful when several consumer can use the data. In both case, the lock is held by only one thread at a time, so only one can run at a given moment.
| Pros | Lock is constantly held | 
|---|---|
| Cons | Even trickier than Pulse | 
| Since | .NET Framework 1.0 | 
| Alt. | ManualResetEvent, ManualResetEventSlim, ReaderWriterLock, ReaderWriterLockSlim | 
| Links | MSDN: Monitor.PulseAll Method | 
20. Mutex
Mutex provides mutual exclusion at the operating system level. Named Mutex can be used from different processes.
readonly Mutex mutex = new Mutex();
    
mutex.WaitOne();
// use shared resource...
mutex.ReleaseMutex();
| Pros | Can work accross processes | 
|---|---|
| Cons | Slow (kernel mode) | 
| Since | .NET Framework 1.0 | 
| Alt. | lock statement, Monitor.Lock()/.Exit() | 
| Links | MSDN: Mutex Class | 
21. ReaderWriterLock
ReaderWriterLock is designed to be used when a single thread can write and several threads can read concurrently.
readonly ReaderWriterLock rwl = new ReaderWriterLock();
“Writer” threads:
 rwl.AcquireWriterLock(timeout);
 // write...
 rwl.ReleaseWriterLock();
“Reader” threads:
rwl.AcquireReaderLock(timeout);
// read...
rwl.ReleaseWriterLock();
| Pros | Clean dedicated solution | 
|---|---|
| Cons | Replaced by ReaderWriterLockSlim in .NET 3.5 | 
| Since | .NET Framework 1.0 | 
| Alt. | ReaderWriterLockSlim, CondurrentQueue | 
| Links | MSDN: ReaderWriterLock Class | 
22. ReaderWriterLockSlim
ReaderWriterLockSlim is an upgraded version of ReaderWriterLock with better performance and simpler rules.
readonly ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
“Writer” threads:
rwl.EnterWriteLock();
// write...
rwl.ExitWriteLock();
“Reader” threads:
rwl.EnterReadLock();
// read...
rwl.ExitReadLock();
| Pros | Even better than ReaderWriterLock | 
|---|---|
| Cons | None | 
| Since | .NET Framework 3.5 | 
| Alt. | ReaderWriterLock, ConcurrentQueue | 
| Links | MSDN: ReaderWriterLockSlim Class | 
23. Semaphore
Semaphore is used to limit the number of threads that access to a shared resources. Each time the semaphore is acquired, the counter decrements; when the counter is zero the thread is blocked. Named Semaphore can be used from different processes.
readonly Semaphore semaphore = new Semaphore(START, MAX);
semaphore.WaitOne();
// use shared resource...
semaphore.Release();
| Pros | Can work accross processes | 
|---|---|
| Cons | Slow (kernel mode) | 
| Since | .NET Framework 2.0 | 
| Alt. | SemaphoreSlim, CountdownEvent | 
| Links | MSDN: Semaphore Class | 
24. SemaphoreSlim
SemaphoreSlim is a lightweight version of Semaphore. It’s a user mode lock, no kernel object is involved.
readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
semaphore.Wait();
// use shared resource...
semaphore.Release();
| Pros | Fast (user mode) | 
|---|---|
| Cons | Not suitable for long waits. Can't work across processes | 
| Since | .NET Framework 4 | 
| Alt. | Semaphore, CountdownEvent | 
| Links | MSDN: SemaphoreSlim Class StackOverflow: Choose between Semaphore and SemaphoreSlim | 
25. SpinLock
SpinLock is a lightweight version of Monitor.Enter/Exit. It’s a user mode lock, no kernel object is involved.
SpinLock spinLock = new SpinLock();
bool lockTaken = false;
spinLock.Enter(ref lockTaken);
// use shared resource...
spinLock.Exit();
| Pros | Light (it's a struct) and fast (user mode) | 
|---|---|
| Cons | Not suitable for long waits | 
| Since | .NET Framework 4 | 
| Alt. | Monitor.Enter()/.Exit(), lock statement | 
| Links | MSDN: SpinLock Structure | 
26. SpinWait
The SpinWait class allows to block until a specified condition is met, ie until some function return true. Despite the word “spin” in its name, SpinWait is an hybrid construct and will yield when the wait is long.
The long version:
SpinWait spinWait = new SpinWait();
while (!someCondition)
{
  sw.SpinOnce();
}
The short version:
SpinWait.SpinUntil(() => someCondition);
| Pros | Yields CPU | 
|---|---|
| Cons | None | 
| Since | .NET Framework 4 | 
| Alt. | CancellationToken, ManualResetEvent, ManualResetEventSlim | 
| Links | MSDN: SpinWait Structure | 
27. Thread.MemoryBarrier
Thread.MemoryBarrier() inserts a full memory barrier (aka full memory fence) which prevents the CLR or the processor from reordering the instructions.
Interlocked.MemoryBarrier is a synonym for Thread.MemoryBarrier() added in .NET framework 4.5.
// ensure 'first' is set before 'second'
first = true;
Thread.MemoryBarrier();
second = true;
| Pros | Non blocking operation | 
|---|---|
| Cons | Very tricky | 
| Since | C# 1.0 | 
| Alt. | Thread.VolatileRead(), Thread.VolatileWrite(), Volatile.Read(), Volatile.Write() | 
| Links | MSDN: Thread.MemoryBarrier Method MSDN: Interlocked.MemoryBarrier Method Threading in C#, by Joe Albahari Wikipedia: Memory barrier | 
28. Thread.VolatileRead()/Write()
Thread.VolatileRead() and Thread.VolatileWrite() guaranties that concurrent threads share an up-to-date version of a field, regardless of the number of processors or the state of processor cache.
These methods are obsolete and are now replaced by Volatile.Read() and Volatile.Write(). The difference is that the new versions insert half memory barrier instead of a full memory barrier, making them more efficient.
int myField;
// some thread
fieldValue = Thread.VolatileRead(ref myField);
// some other thread
Thread.VolatileWrite(ref myField, newValue);
| Pros | Doesn't block the thread | 
|---|---|
| Cons | Obsolete, replaced by Volatile class | 
| Since | .NET Framework 1.1 | 
| Alt. | volatile keywork, Volatile.Read(), Volatile.Write(), MemoryBarrier | 
| Links | StackOverflow: Volatile vs VolatileRead/Write? MSDN: Thread.VolatileRead Method MSDN: Thread.VolatileWrite Method | 
29. volatile keyword
When you declare a field as volatile, the compiler assumes that the fields is accessed concurrently by several thread, and will disable optimization accordingly. AFAIK, this is the same thing as accessing the field via Volatile.Read() and Volatile.Write().
| Pros | Less error prone than Volatile class | 
|---|---|
| Cons | Only works for fields of certains types | 
| Since | C# 1.0 | 
| Alt. | Volatile.Read(), Volatile.Write() | 
| Links | MSDN: volatile (C# Reference) Threading in C#, by Joe Albahari Coverity: Reordering optimizations StackOverflow: What is the purpose of 'volatile' keyword in C# Eric Lippert's blog: Atomicity, volatility and immutability... Joe Duffy's Blog: Sayonara volatile | 
30. Volatile.Read()/Write()
Volatile.Write() ensures that a value written to a memory location is immediately visible to all processors. Volatile.Read() obtains the very latest value written to a memory location by any processor.
int myField;
// some thread
fieldValue = Volatile.Read(ref myField);
// some other thread
Volatile.Write(ref myField, newValue);
Both methods insert a half memory barrier that prevents the processors from reordering the instructions:
- If a read or write appears after Volatile.Read(), the processor cannot move it up.
- If a read or write appears before Volatile.Write(), the processor cannot move it down.
| Pros | Doesn't block the thread | 
|---|---|
| Cons | Very hard to get it 100% right | 
| Since | .NET Framework 4.5 | 
| Alt. | volative keyword, Thread.VolatileRead(), Thread.VolatileWrite() | 
| Links | StackOverflow: Volatile vs VolatileRead/Write? MSDN: Volatile Class Eric Lippert's blog: Atomicity, volatility and immutability... Joe Duffy's Blog: Sayonara volatile |