MNW #008: Introduction To Locking And Concurrency Control in .NET 6

Oct 22 2022

4 min read

A big thank you to this week's sponsor who helps keep this newsletter free to the reader:

This week's issue is sponsored by QuadSpinner.


In this week's newsletter, we'll see how we can work with locking in .NET 6.

We won't talk about how the lock is actually implemented at the operating system level. We will focus on application-level locking mechanisms instead.

Locking allows us to control how many threads can access some piece of code. Why would you want to do this?

Usually because you want to protect access to expensive resources, and you need the concurrency control that locking enables.

We will use a simple BankAccount class with a Deposit method to illustrate how to implement locking.

The C# Lock Statement

The C# language supports locking with the lock statement. You can use the lock statement to define a code block that only one thread can access.

The lock statement acquires a mutual-exclusion lock (mutex) for a given object, executes the statement block, and releases the lock.

lock(_lock)
{
   // Your code...
}

Here _lock is a reference type, usually an object instance.

Let's see how we can implement the BankAccount class using the lock statement:

public class BankAccount
{
   private static readonly object _lock = new();
   private decimal _balance;

   public void Deposit(decimal amount)
   {
      lock(_lock)
      {
         _balance += amount;
      }
   }
}

The first thread to reach and execute the lock statement will be allowed to update the _balance. Any other threads will block until the lock is released.

Locking With Semaphore

The Semaphore class is another option we can use to achieve the same effect.

We'll use the Semaphore constructor to set the initialCount to 0, which means that the Semaphore is open at the start. And we will also set the maximumCount to 1, which means that only one thread is allowed to enter the Semaphore.

Let's see how we can implement the BankAccount class using the Semaphore:

public class BankAccount
{
   private static readonly Semaphore _semaphore = new(
      initialCount: 0,
      maximumCount: 1);

   private decimal _balance;

   public void Deposit(decimal amount)
   {
      _semaphore.WaitOne();

      _balance += amount;

      _semaphore.Release();
   }
}

To enter the Semaphore, we have to call the WaitOne method.

If no thread was previously inside, our thread is allowed to enter the Semaphore and update the balance.

After updating the balance, we call the Release method to release the Semaphore for other threads that might be waiting.

Asynchronous Locking With SemaphoreSlim

What if we wanted to call an asynchronous method in a locked context?

We can't use the lock statement as it doesn't support asynchronous calls. Awaiting an asynchronous call inside a lock statement will cause a compilation error.

The Semaphore class can solve this problem.

But I want to show you another option that we have, SemaphoreSlim. It's a lightweight alternative to the Semaphore class and has async methods.

Let's see how we can implement the BankAccount class using SemaphoreSlim:

public class BankAccount
{
   private static readonly SemaphoreSlim _semaphore = new(
      initialCount: 0,
      maximumCount: 1);

   private decimal _balance;

   public async Task Deposit(decimal amount)
   {
      await _semaphore.WaitAsync();

      _balance += amount;

      _semaphore.Release();
   }
}

Notice that I updated the Deposit method to return a Task.

This time, we're calling WaitAsync to block the current thread until it can enter the semaphore.

After updating the balance, we call the Release method to release the SemaphoreSlim like in the previous example.

Are There Other Options For Locking in .NET?

So far I mentioned three options to implement locking:

However, .NET has other classes for concurrency control that you can explore like Monitor, Mutex, ReaderWriterLock and many more.

I hope you enjoyed this brief introduction to a very complex topic.


Whenever you're ready, there are 3 ways I can help you 🔥

  1. To access all of the source code that I use in my YouTube videos support me on Patreon
  2. For more practical & in-depth coding examples subscribe to my YouTube channel
  3. Promote yourself to 11k+ subscribers by sponsoring this newsletter (booked out 2 months)

Special Offers 📢

About the Newsletter

11k+ subscribers get one tip to improve their software engineering and .NET skills every Saturday morning.

Connect with Me

Join 11,774 .NET engineers getting 1 actionable .NET tip every Saturday

Actionable tips

Easy to implement

5 minute read

Free, forever