# Introduction To Locking And Concurrency Control in .NET 6

> In this week's newsletter, we'll see how we can work with locking in .NET 6. We won't cover how the lock is implemented at the operating system level, only the application-level mechanisms: the lock statement, Semaphore, and SemaphoreSlim.

Published: 2022-10-22. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/introduction-to-locking-and-concurrency-control-in-dotnet-6

Locking is how you stop two threads from running the same block of code at the same time, so concurrent updates to a shared resource stay correct.
.NET gives you the `lock` statement and the `Semaphore` class for synchronous code, and `SemaphoreSlim` when the critical section contains `async` calls.
`Monitor`, `Mutex`, and `ReaderWriterLock` are the other options.

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.
Instead, I will focus on application-level **locking** mechanisms.

**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](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/lock).
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.

```csharp
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:

```csharp
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`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.semaphore?view=net-6.0)
class is another option we can use to achieve the same effect.

We'll use the `Semaphore` constructor to set the `initialCount` to 1,
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`:

```csharp
public class BankAccount
{
   private static readonly Semaphore _semaphore = new(
      initialCount: 1,
      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`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.semaphoreslim?view=net-6.0).
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`:

```csharp
public class BankAccount
{
   private static readonly SemaphoreSlim _semaphore = new(
      initialCount: 1,
      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:

- [`lock` statement](#the-c-lock-statement)
- [`Semaphore`](#locking-with-semaphore)
- [`SemaphoreSlim`](#asynchronous-locking-with-semaphoreslim)

However, **.NET** has other classes for **concurrency control** that you can
explore like
[`Monitor`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.monitor?view=net-6.0),
[`Mutex`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=net-6.0),
[`ReaderWriterLock`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.readerwriterlock?view=net-6.0)
and many more.

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

---

## Frequently asked questions

### What is locking in .NET?

Locking controls how many threads can access a piece of code at the same time. You typically use it to protect access to expensive resources where concurrent updates, like changing a bank account balance, need concurrency control.

### How does the C# lock statement work?

The lock statement acquires a mutual-exclusion lock (mutex) on a reference type object, executes the statement block, and releases the lock. The first thread to reach it runs the block while other threads block until the lock is released.

### Why can you not await inside a lock statement?

The lock statement does not support asynchronous calls, and awaiting inside one causes a compilation error. Use SemaphoreSlim instead: await WaitAsync before the critical section and call Release when you are done.

### What is the difference between Semaphore and SemaphoreSlim?

Both limit how many threads can enter a section, configured through an initial and maximum count. SemaphoreSlim is a lightweight alternative to Semaphore and exposes async methods like WaitAsync, making it the right choice around asynchronous code.

### What other concurrency control options does .NET have?

Beyond the lock statement, Semaphore, and SemaphoreSlim, .NET offers classes like Monitor, Mutex, and ReaderWriterLock, plus many more concurrency control primitives you can explore for specialized scenarios.
