Have you ever tried to await a task inside a lock() block? In C#, this statement is invalid:

lock (lockObject) { await Task.Delay(1000); }

The lock keyword can only be used to synchronize synchronous code. From MSDN:

An await expression cannot occur in the body of a synchronous function, in a query expression, in the block of a lock statement, or in an unsafe context.

Since the introduction of C# 5, async / await is used pretty much everywhere. And why not? The compiler does the difficult work that the developer used to do, and the application retains a logical structure that resembles synchronous code. As a result, you get all the advantages of asynchronous programming with a fraction of the effort.

The problem is that the need to synchronize asynchronous code blocks is coming up quite often. Eric Lippert notes that the reason that this is not implemented by the compiler team is not because it's difficult to implement, but rather to protect the developer from making mistakes ; awaiting inside a lock is a recipe for producing deadlocks.

Introducing the Mutex and the Semaphore

In simple terms, a semaphore is a data type that is used for synchronizing access from multiple threads. Semaphores are a useful tool in the prevention of race conditions. There are two types of semaphores:

Counting Semaphores: As the name implies, counting semaphores allow a number of simultaneous threads to access a shared resource (up to a maximum number you specify). When threads request access to the resource, the semaphore count decrements and when they release it, it increments back again. Binary Semaphores or Mutex: A Mutex is essentially a semaphore with a value of 1. A Mutex cannot be released to more than one thread at the same time; it provides mutual exclusion (hence the name). As long as someone has the mutex, the others must wait.

.NET Semaphore and SemaphoreSlim

The System.Threading.Semaphore class is a wrapper around the Win32 semaphore object (counting semaphores). This is a system wide semaphore, so it can be used between multiple processes.

On the other hand, the System.Threading.SemaphoreSlim is a lightweight, fast semaphore that is provided by the CLR and used for waiting within a single process when wait times are expected to be very short.

Replacing the Lock with a Semaphore

So now, that we know what a Semaphore is we may go ahead and replace the lock with a Semaphore. In our case, the SemaphoreSlim class is the ideal data type to use since we will be using it in a single process.

It is vital to always release the Semaphore when you are ready, this is why it is suggested to be placed inside a try ... finally clause .

Calling WaitAsync on the semaphore produces a task that will be completed when that thread has been granted access to the Semaphore.

//Instantiate a Singleton of the Semaphore with a value of 1. This means that only 1 thread can be granted access at a time. static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1,1);