The lock statement was introduced in c# 2.0 to prevent multi threads access to an object at the same time.
In async programming model the goal of the locks is to limit the number of concurrent execution of a block of code to a defined number.
While Microsoft introduced a lot of threads synchronization mechanisms , we will only discuss the SemaphoreSlim in this article.
Example
class DataManger
{
private DbSet<string> _users;
public DataManger(DbSet<string> users)
{
_users = users;
}
public async Task AddUser(string username)
{
await _users.AddAsync(username);
}
}
for some reasons we need to limit the number of calls to addUser method to 3 calls at a time.
static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(3);
public async Task AddUser(string username)
{
await _semaphoreSlim.WaitAsync();
await _users.AddAsync(username);
_semaphoreSlim.Release();
}
static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(3);
the semaphoreSlim
act as lock , we initialize it by setting the maximum number of concurrent request to 3 requests
await _semaphoreSlim.WaitAsync();
if the number of current concurrent requests is less then 3 , it will decrease it by 1, otherwise it will wait until one of the other threads release.
_semaphoreSlim.Release();
simply release the semaphore so any pending requests or upcoming requests can execute.
Using Aspect Oriented programming
while the semaphoreSlim
look easy to use , it come with a cost as it introduce more boilerplates to the code (the semaphore declaration , the waitasync statement at the start of the method and the release at the end) and even more complexities imagine exceptions in _users.AddAsync
may be a better idea will to use try finally block.
This can have some dramatic consequences on your code complexity as you will have to declare a semaphore per every method you which to limit access to it.
As a solution to make my code cleaner, I prefer using Postsharp aspects
[Serializable]
public class MethodLockedAttribute : MethodInterceptionAspect
{
private int maximum_concurrency_number;
private static ConcurrentDictionary<int,SemaphoreSlim> SemaphoreSlimRepo=new ConcurrentDictionary<int, SemaphoreSlim>();
public MethodLockedAttribute(int maximumConcurrencyNumber)
{
maximum_concurrency_number = maximumConcurrencyNumber;
}
public override async Task OnInvokeAsync(MethodInterceptionArgs args)
{
SemaphoreSlim semaphore=new SemaphoreSlim(maximum_concurrency_number);
SemaphoreSlimRepo.GetOrAdd(args.Method.GetMetadataToken(), semaphore);
await semaphore.WaitAsync();
try
{
await args.ProceedAsync();
}
finally
{
semaphore.Release();
}
}
}
and decorate the target method to be :
[MethodLocked(3)]
public async Task AddUser(string username)
{
await _users.AddAsync(username);
}