Introduction to Monitor Class in C#
Tags: csharp, multithreading, criticalsection, threadsync
Published: 2024-05-13 10:15:07

In the previous article, we have talked about [race condition problem](https://dev.to/rasulhsn/deep-dive-into-race-condition-problem-in-net-3ac6). So, in this article we will focus on one of the solutions (using critical section), the Monitor class, which is a mutual-exclusive thread synchronization construct.

The Monitor class is built on dotNET’s FCL (Framework Class Library) infrastructure. In general, it provides to achieve thread safety.

## Thread Synchronization In Critical Section

Thread synchronization is used to prevent corruption when multiple threads access shared data at the same time; Because thread synchronization is all about timing.

The **System.Threading.Monitor** class basically helps us to avoid the race condition problem by protecting the critical section.

Let’s think that we have a code block that has shared resource (variable or any state) and this resource is using in executed context by multithread. So, in this case, we need to understand that the potential race condition problem exists for us. And also that code block is a **critical section** for us.

The Monitor class provides threads to enter one by one into the code block, i.e. it accepts the thread into the critical section and executes the block with a single thread, then releases the thread. This means that all the other threads must wait and halt the execution until the locked section is released. And this cycle will continue in the same way.

In other words, it helps us to synchronize the execution of threads between each other.

## How to Use The Monitor Class?

Let’s simulate the race condition problem and then prevent to it with Monitor Class.

```csharp
public class Program
{
    // A basic field for the simulation of shared data
    static int _sharedField = 0;

    public static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            ThreadPool.QueueUserWorkItem((s) => IncrementTheValue());
        }

        Console.WriteLine("Main method is finished!");
        Console.Read();
    }

    // Method variable will give inconsistent result
    static void IncrementTheValue()
    {
        if (_sharedField < 5)
        {
            Thread.Sleep(55); // just for simulation execution time!
            _sharedField += 1;
            Console.WriteLine($"Field: {_sharedField} | Thread: {Thread.CurrentThread.ManagedThreadId}");
        }
        else
        {
            Console.WriteLine($"Field count is complited {_sharedField} | Thread Id is {Thread.CurrentThread.ManagedThreadId}");
        }
    }
}
```
The code above shows that we have a main method that creates 10 threads and they independently executes the IncrementTheValue method. And the method steps are:

1. if **_sharedField** is less than 5 then enter the code block
2. Execution time simulation with **Thread.Sleep(55)** (fake operation)
3. Increment the value of **_sharedField**
4. Write into the console **_sharedField** value and current thread id

Well, if you run the code, you will see that the result of the **_sharedField** variable is above 5. So, we are caught in a race condition problem.

Hmm, let’s avoid this problem. First of all, we need to identify our **critical section area**, in our case, it is the **IncrementTheValue** method completely. Because the method uses IF and ELSE blocks. So, we need to guarantee that our IF block will not participate in the case of racing threads. Otherwise, the race condition problem will continue.

```csharp
public class Program
{
    // Should be reference type
    // Should be private
    static readonly object _LOCKREF = new object();

    // A basic field for the simulation of shared data
    static int _sharedField = 0; 

    public static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            ThreadPool.QueueUserWorkItem((s) => IncrementTheValue());
        }

        Console.WriteLine("Main method is finished!");
        Console.Read();
    }

    static void IncrementTheValue()
    {
        try
        {
            Monitor.Enter(_LOCKREF);

            // Critical section start point
            if (_sharedField < 5)
            {
                Thread.Sleep(55); // just for simulation execution time!
                _sharedField += 1;
                Console.WriteLine($"Field: {_sharedField} | Thread: {Thread.CurrentThread.ManagedThreadId}");
            }// Critical section end point
            else
            {
                Console.WriteLine($"Field count is complited {_sharedField} | Thread Id is {Thread.CurrentThread.ManagedThreadId}");
            }
        }
        finally
        {
            // Ensure that the lock is released
            Monitor.Exit(_LOCKREF);
        }
    }
}
```
In the code above, we see that **Monitor.Enter** method is declared and this method provides a wait-based (the waiting is implemented effectively, because avoids the CPU wasting) synchronization mechanism that allows only one thread to access the critical section code.

For the case of exceptions, we are using **try + finally** blocks. Because the Monitor class must release the lock at the end of any situation. So, we see that in the finally block has been declared **Monitor.Exit** method which helps us to release the lock from critical section.

As a result, with the code above, we have solved our race condition problem through the Monitor class. And now our code is thread-safe!

## Input Parameter of Monitor.Enter Method

In the code above, where we avoid the race condition problem, there is an instance of the Object class its **_LOCKREF** which taking input into the **Monitor.Enter** method.

In our example code, the **_LOCKREF** instance helps us lock the thread. That is, it provides a reference to distinguish the context. So, you need to be careful when using **Monitor.Enter** method, that the reference to be used as an input parameter is closed outside the class.

So, we can understand that the input parameter of **Monitor.Enter** is some flag for understanding the locking state (in a simple term).

Note that if the **_LOCKREF** instance is manipulated or opened outside, it may cause a **deadlock**. Also, do not use the **“this”** keyword instead of **_LOCKREF** as it may cause **deadlock**, too.

## LockTaken Pattern

As we can see in our code example, there is a **try + finally** block. So, in a multithreaded scenario, what happens if one of the threads enters the code block and an exception is thrown (or the thread is aborted) in the first step where it enters **‘try’** before reaching the **Monitor.Enter** method?

Hmm, I think almost everyone realized that the finally block will work in any case. So, if the thread without acquiring the lock tries to release the lock; it will receive **System.Threading.SynchronizationLockException** exception. Well, to avoid any case of this situation we need to use the **LockTaken Pattern**.

```csharp
static void IncrementTheValue()
{
    bool lockTaken = false;

    try
    {
        Monitor.Enter(_LOCKREF, ref lockTaken);

        // Critical section start point
        if (_sharedField < 5)
        {
            Thread.Sleep(55); // just for simulation execution time!
            _sharedField += 1;
            Console.WriteLine($"Field: {_sharedField} | Thread: {Thread.CurrentThread.ManagedThreadId}");
        }
        else
        {
            Console.WriteLine($"Field count is complited {_sharedField} | Thread Id is {Thread.CurrentThread.ManagedThreadId}");
        }
        // Critical section end point
    }
    finally
    {         
        if (lockTaken)
        {
            // Ensure that the lock is released
            Monitor.Exit(_LOCKREF);
            Console.WriteLine($"Thread Id is {Thread.CurrentThread.ManagedThreadId} released from the lock!");
        }
    }
}
```
To implement this pattern, the **Monitor.Enter** method has an overload, it accepts 2 parameters. One of them is the lock reference instance, the other accepts a boolean variable.

As we see that, in the first line of the code above, there is a declared boolean variable lockTaken.

**Monitor.Enter** accepts the lockTaken variable with ref keyword. This means that if the method is true, the lock is taken and if false, the lock is not taken. So, **finally** block handles by checking lockTaken variable and in case of true **if** block executes otherwise it does not.

The lockTaken pattern provides safety in case any thread is aborted without acquiring a lock.

## Methods of The Monitor Class

Only the most commonly used methods are described. For more information [Microsoft Documentation](https://learn.microsoft.com/en-us/dotnet/api/system.threading.monitor?view=net-8.0).

1. **Enter(Object)**: Acquires an exclusive lock on the specified object.
2. **Enter(Object, Boolean)**: Acquires an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken.
3. **Exit(Object)**: Releases an exclusive lock on the specified object.
4. **IsEntered(Object)**: Determines whether the current thread holds the lock on the specified object.
5. **TryEnter(Object)**: Attempts to acquire an exclusive lock on the specified object.

In the next article, I will explain the Monitor class in detail. Stay tuned!