.Net CoreDevelopment

C# locks and async Tasks.

C# lock is mechanism to prevent concurrent access to “restricted” resource in multi-threaded environment. In multi-threaded applications locks are used to ensure that the current thread executes a block of code to completion without interruption by other threads. The lock statement obtains a mutual exclusion lock for a given object so that one thread executes the code block at a time and exits the code block after releasing the lock.

In C# apps, standard way to prevent concurrent access to the code is by using lock statement. Usage is relatively straightforward, but (as everything in “multi-threading”) usage can also be tricky, not to say dangerous.

While, lock statement works in “normal” code, async/await does not work with lock. I will use different approach here. Let’s take a look.

C# lock statement.

In C#, there is very simple way how to lock access to a resource. Most commonly, lock statement is used. Lets see an example.

Where _locker is declared as some instance of reference type, e.g.:

private static readonly object _locker = new object();

StartCounting() is function which needs exclusive lock on some resource.

Simple example of lock.

In the next section, I will present simple example how to use lock and how application behaves if lock is used or if locking is not applied. Example below shows counter functions, one with locking and other without. I will show how these two behaves when used in parallel (multi-threaded environment).

With no locking applied, I get:

With lock:

You can spot the difference, second example with lock does not allow “concurrent” access to locked resources, so output is as expected – after first counting is finished, second is started, etc… _counter variable is used only in current context, no conflicts and unpredicted results.

What about async/await locking?

Until now everything was fine, just classical lock mechanism. But what about locking of async/await methods?

Just to summarize, async/await was introduced in C# 5 as part of Task-based Asynchronous Pattern (TAP). Noways, TAP is the recommended asynchronous design pattern for new development, therefore It’s widely used.

Let’s investigate locks in async/await. Logical approach would be to use similar approach as above, e.g.:

but, this does not work. Compiler has problems with this, I get:

Cannot await in the body of lock statement

C# compiler

So, what now?

Let’s introduce SemaphoreSlim! SemaphoreSlim represents a lightweight alternative to Semaphore that limits the number of threads that can access a resource or pool of resources concurrently. In this case this objects handles locking.

Pattern of locking of async/await methods with SemaphoreSlim is:

where _semaphoreSlim is defined and initialized as:

Just a note here: always use SemaphoreSlim.WaitAsync() and SemaphoreSlim.Release() with try-catch-finally statement.

Simple example of async/await lock.

So, no-lock version of async/await method returns this:

With SemaphoreSlim locker, output looks like this:

From upper outputs we can clearly see effect of locking in async/await methods in multi-threading environments.

Conclusion.

C# lock statement is used for mutual-exclusion lock for a given object, executes a statement block, and then releases the lock.

For locking TAP based methods, SemaphoreSlim is one way to do mutual-exclusion locking.

In this blog post, I quickly presented how both mechanisms can be used on simple examples.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.