[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:

  1. ArrayList.Synchronized method
  2. AutoResetEvent class
  3. Barrier class
  4. BlockingCollection class
  5. CancellationToken struct
  6. ConcurrentBag class
  7. ConcurrentDictionary class
  8. ConcurrentQueue class
  9. ConcurrentStack class
  10. CountdownEvent class
  11. EventWaitHandle class
  12. Interlocked operations
  13. lock statement
  14. ManualResetEvent class
  15. ManualResetEventSlim class
  16. MethodImplOptions.Synchronized attribute
  17. Monitor.Enter and .Exit methods
  18. Monitor.Pulse method
  19. Monitor.PulseAll method
  20. Mutex class
  21. ReaderWriterLock class
  22. ReaderWriterLockSlim class
  23. Semaphore class
  24. SemaphoreSlim class
  25. SpinLock class
  26. SpinWait class
  27. Thread.MemoryBarrier method (and Interlocked.MemoryBarrier)
  28. Thread.VolatileRead and .VolatileWrite methods
  29. volatile keyword
  30. Volatile.Read and Write methods

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