Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#

Async Lock In C#

Rate me:
Please Sign up or sign in to vote.
4.27/5 (4 votes)
16 Sep 2019CPOL1 min read 13.6K   6   2
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.

Image 1

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Austria Austria
I am a software engineer at PlanRadar currently living in Vienna, Austria. My interests range from technology to web development. I am also interested in programming, xamarin, and mobile development.

Comments and Discussions

 
PraiseThanks for sharing Pin
AnotherKen18-Sep-19 8:30
professionalAnotherKen18-Sep-19 8:30 
BugAt best unclear... Pin
spi18-Sep-19 2:40
professionalspi18-Sep-19 2:40 
"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."

What does this mean?

It's not because Microsoft did not implement an AsyncLock (here an example of an independent solution: GitHub - neosmart/AsyncLock: An async/await-friendly lock for .NET, complete with asynchronous waits, safe reëntrance, and more.[^]) that "locks" suddenly became "semaphores"! (The SemaphoreSlim was just one of the first available basic concurrent objects to support asynchronous operations.)

One more (important) thing: did you notice that this "2.0 lock aspect" is almost NEVER used by anybody? That a huge majority of developers uses the lock( _privateObject ) { } statement inside their implementations?
It's because using the "external lock" (as an "aspect" as you name it) is dangerous (see for instance: Choosing What To Lock On[^]).

Hope that helps.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.