65.9K
CodeProject is changing. Read more.
Home

Async Lock In C#

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (4 votes)

Sep 17, 2019

CPOL

1 min read

viewsIcon

14707

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..

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);
       }