So we all have been in a situation when we were writing our scalable and maintainable C# applications taking advantage of async await
and we wanted to synchronise access to shared resources using lock
block. Only to find out that it is impossible to await
when we are inside lock
block. If we try we get an error like this: Cannot await in the body of a lock statement. This is purposely not allowed because a lot of bad things⢠can happen if we did so. For example after await
we can continue in a different thread and we might end up unlocking a different thread than the one that took that lock in a first place. There are some other problems related to awaiting inside lock
like lock ordering inversions but I won’t go into details. Instead I’ll offer a solution to this problem!
We will take advantage of SemaphoreSlim
class that was introduced in .NET Framework 4.0 in System.Threading
namespace. If we think about it lock
is just a binary semaphore protecting a critical section that is a body of our lock
block. And SemaphoreSlim
is counting semaphore that supports async await
. So the code for our AsyncLock
would look something like this:
[code language=”csharp” gutter=”false”]
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MyApp
{
public class AsyncLock : IDisposable
{
private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
public async Task<AsyncLock> LockAsync()
{
await _semaphoreSlim.WaitAsync();
return this;
}
public void Dispose()
{
_semaphoreSlim.Release();
}
}
}
[/code]
A sample usage would look like this:
[code language=”csharp” gutter=”false”]
private static readonly AsyncLock _mutex = new AsyncLock();
using(await _mutex.LockAsync())
{
// Critical section… You can await here!
}
[/code]
Here we are creating an instance of SemaphoreSlim
and wrapping it inside IDisposable
so that we could use using
statement for our critical section. Semaphore has a max count of 1 meaning that only one thread can hold a lock without blocking (asynchronously) at any given time. We are decreasing a CurrentCount
property of the semaphore every time we call LockAsync
and increasing it every time Dispose
is called. And thats it! The usage is very simple and the syntax is very similar to the one of a lock
statement.