Monitor Class in C#

Monitor Class in C# with Examples

In this article, I am going to discuss How to Protect the Shared Resources in Multithreading using Monitor Class in C# with Examples. Please read our previous article before proceeding to this article where we discussed How to Protect the shared Resource using Locking in C# from Concurrent Access with Examples. As part of this article, we are going to discuss the following pointers.

  1. Understanding the Monitor Class in C#.
  2. How to Protect the Shared Resources in Multithreading using Monitor Class in C#?
  3. Understanding Different Methods of Monitor class with examples.
  4. Difference between Monitor and lock in C#.

We already discussed both Monitor and Lock are used to provide thread safety to a shared resource in a multithreaded application in C#. In our previous article, we see how to use locking to achieve thread safety in a multi-thread environment. So, let us understand the Monitor class and its methods in detail to understand how to protect the shared resource using the monitor class in C# with Examples.

Monitor Class in C#:

The Monitor Class in C# Provides a mechanism that synchronizes access to objects. Let us simplify the above definition. In simple words, we can say that, like the lock, we can also use this Monitor Class to protect the shared resources in a multi-threaded environment from concurrent access. This can be done by acquiring an exclusive lock on the object so that only one thread can enter the critical section at any given point in time.

The Monitor is a static class and belongs to the System.Threading namespace. As a static class, it provides a collection of static methods as shown in the below image. Using these static methods we can provide access to the monitor associated with a particular object.

Protecting Shared Resources in Multithreading Using Monitor Class in C#

Let us understand the methods of the Monitor class.

Enter(object obj): This method acquires an exclusive lock on the specified object. This method takes one object parameter on which to acquire the monitor lock. If the parameter obj is null, then it will throw ArgumentNullException.

Enter(object obj, ref bool lockTaken): This method also acquires an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken. Here, the parameter obj specifies the object on which to wait. The parameter lockTaken specifies the result of the attempt to acquire the lock, passed by reference. The input must be false. The output is true if the lock is acquired; otherwise, the output is false. The output is set even if an exception occurs during the attempt to acquire the lock. Note If no exception occurs, the output of this method is always true. It will throw ArgumentException if the input to lockTaken is true. It will throw ArgumentNullException if the obj parameter is null.

TryEnter Methods:

There are six overloaded versions of the TryEnter method available in the Monitor class. They are as follows:

  1. public static bool TryEnter(object obj, TimeSpan timeout): Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object.
  2. public static void TryEnter(object obj, int millisecondsTimeout, ref bool lockTaken): Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken.
  3. public static void TryEnter(object obj, ref bool lockTaken): Attempts to acquire an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken.
  4. public static bool TryEnter(object obj): Attempts to acquire an exclusive lock on the specified object.
  5. public static bool TryEnter(object obj, int millisecondsTimeout): Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object.
  6. public static void TryEnter(object obj, TimeSpan timeout, ref bool lockTaken): Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken.

All these methods are also used to acquire an exclusive lock on the specified object. Further, if you notice all these methods return type is bool. So, the TryEnter() method returns true if the current thread acquires the lock; otherwise, false. Following are the parameters in by TryEnter method.

  1. object obj: All the six overloaded versions take one object type parameter which specifies the object on which to acquire the lock. If the object parameter this method takes is null, then it will throw ArgumentNullException.
  2. TimeSpan timeout: Some TryEnter() methods take TimeSpan timeout as a parameter and this parameter specifies a System.TimeSpan represents the amount of time to wait for the lock. A value of -1 millisecond specifies an infinite wait. It will throw ArgumentOutOfRangeException if the value of timeout in milliseconds is negative and is not equal to System.Threading.Timeout.Infinite (-1 millisecond), or is greater than System.Int32.MaxValue.
  3. int millisecondsTimeout: Again, two overloaded versions take int millisecondsTimeout as a parameter and this parameter specifies the number of milliseconds to wait for the lock. It will throw ArgumentOutOfRangeException if millisecondsTimeout is negative, and not equal to System.Threading.Timeout.Infinite.
  4. ref bool lockTaken: Also three overloaded versions take ref bool lockTaken as a parameter and this parameter specifies the result of the attempt to acquire the lock, passed by reference. The input must be false. The output is true if the lock is acquired; otherwise, the output is false. The output is set even if an exception occurs during the attempt to acquire the lock. It will ArgumentException if the input to lockTaken is true.

Note: Both Enter and TryEnter methods are used to acquire an exclusive lock for an object. This action marks the beginning of a critical section. No other thread can enter into the critical section unless it is executing the instructions in the critical section using a different locked object.

Wait Methods of Monitor Class in C#:

There are five overloaded versions of the Wait method available in the Monitor class. They are as follows:

  1. public static bool Wait(object obj): It Releases the lock on an object and blocks the current thread until it reacquires the lock.
  2. public static bool Wait(object obj, TimeSpan timeout): Releases the lock on an object and blocks the current thread until it reacquires the lock. If the specified time-out interval elapses, the thread enters the ready queue.
  3. public static bool Wait(object obj, int millisecondsTimeout): It releases the lock on an object and blocks the current thread until it reacquires the lock. If the specified time-out interval elapses, the thread enters the ready queue.
  4. public static bool Wait(object obj, TimeSpan timeout, bool exitContext): It releases the lock on an object and blocks the current thread until it reacquires the lock. If the specified time-out interval elapses, the thread enters the ready queue. Optionally exits the synchronization domain for the synchronized context before the wait and reacquires the domain afterward.
  5. public static bool Wait(object obj, int millisecondsTimeout, bool exitContext): It releases the lock on an object and blocks the current thread until it reacquires the lock. If the specified time-out interval elapses, the thread enters the ready queue. This method also specifies whether the synchronization domain for the context (if in a synchronized context) is exited before the wait and reacquired afterward.

All these Wait Methods are used to release the lock on an object and block the current thread until it reacquires the lock. All these methods return type is boolean. So, these methods return true if the call returned because the caller reacquired the lock for the specified object. This method does not return if the lock is not reacquired. Following are the parameter used in the Wait method.

  1. object obj: The object on which to wait. It will throw ArgumentNullException if the obj parameter is null.
  2. TimeSpan timeout: A System.TimeSpan represents the amount of time to wait before the thread enters the ready queue. It will throw ArgumentOutOfRangeException if the value of the timeout parameter in milliseconds is negative and does not represent System.Threading.Timeout.Infinite (-1 millisecond), or is greater than System.Int32.MaxValue.
  3. int millisecondsTimeout: The number of milliseconds to wait before the thread enters the ready queue. It will throw ArgumentOutOfRangeException if the value of the millisecondsTimeout parameter is negative, and is not equal to System.Threading.Timeout.Infinite.
  4. bool exitContext: true to exit and reacquire the synchronization domain for the context (if in a synchronized context) before the wait; otherwise, false.
  5. ref bool lockTaken: The result of the attempt to acquire the lock, passed by reference. The input must be false. The output is true if the lock is acquired; otherwise, the output is false. The output is set even if an exception occurs during the attempt to acquire the lock.

Note: The Wait methods are used to release the lock on an object and permit other threads to lock and access the object by blocking the current thread until it reacquires the lock. The calling thread waits while another thread accesses the object. Pulse signals are used to notify waiting threads about changes to an object’s state.

Pulse and PulseAll Method of Monitor Class in C#:

The above two methods are used to send a signal to one or more waiting threads. The signal notifies a waiting thread that the state of the locked object has changed, and the owner of the lock is ready to release the lock.

  1. Pulse(object obj): This method notifies a thread in the waiting queue of a change in the locked object’s state. The obj parameter specifies the object a thread is waiting for. If the obj parameter is null, then it will throw ArgumentNullException.
  2. PulseAll(object obj): This method notifies all waiting threads of a change in the object’s state. The obj parameter specifies the object that sends the pulse. If the obj parameter is null, then it will throw ArgumentNullException.
Exit():

The Exit method is used to release the exclusive lock from the specified object. This action marks the end of a critical section protected by the locked object.

  1. Exit(object obj): This method releases an exclusive lock on the specified object. The parameter obj specifies the object on which to release the lock. It will throw ArgumentNullException if the obj parameter is null.
IsEntered() Method: 
  1. IsEntered(object obj):  It Determines whether the current thread holds the lock on the specified object. The parameter obj specifies the object to test. It returns true if the current thread holds the lock on obj; otherwise, false. If obj is null, then it will throw ArgumentNullException.
Example to understand Monitor Class in C# to Protect Shared Resource from Concurrent Access:

The following is the syntax to use the Enter method of the Monitor class to protect a shared resource in a multithreaded environment from concurrent access in C#. All the methods of the Monitor class are static methods. So, you can see here, we are accessing the Enter and Exit method using the class name.

Monitor.Enter method to protect a shared resource in a multithreaded environment from concurrent access in C#

Let us see an example to understand how to use the Monitor class Enter and Exit method to protect a shared resource in a multithreaded environment in C# from concurrent access.

using System;
using System.Threading;

namespace MonitorDemo
{
    class Program
    {
        static readonly object lockObject = new object();

        public static void PrintNumbers()
        {
            Console.WriteLine(Thread.CurrentThread.Name + " Trying to enter into the critical section");
            Monitor.Enter(lockObject);
            try
            {
                Console.WriteLine(Thread.CurrentThread.Name + " Entered into the critical section");
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(100);
                    Console.Write(i + ",");
                }
                Console.WriteLine();
            }
            finally
            {
                Monitor.Exit(lockObject);
                Console.WriteLine(Thread.CurrentThread.Name + " Exit from critical section");
            }
        }

        static void Main(string[] args)
        {
            Thread[] Threads = new Thread[3];
            for (int i = 0; i < 3; i++)
            {
                Threads[i] = new Thread(PrintNumbers);
                Threads[i].Name = "Child Thread " + i;
            }

            foreach (Thread t in Threads)
            {
                t.Start();
            }

            Console.ReadLine();
        }
    }
}
Output:

Example to understand Monitor Class in C# to Protect Shared Resource from Concurrent Access

Monitor.Enter(lockObject, ref IslockTaken) Method in C#:

Let us understand the overloaded version of the Enter method. The Monitor.Enter(lockObject, ref IslockTaken) acquires an exclusive lock on the specified object. It then automatically sets a value that indicates whether the lock was taken or not. The second parameter which is a Boolean parameter returns true if the lock is acquired else it returns false. The syntax to use this overloaded version is given below.

How to Protect the Shared Resources in Multithreading using the Monitor class from Concurrent Access in C# with Examples

The following example shows how to use Enter(lockObject, ref IslockTaken) method of the Monitor class in C#.

using System;
using System.Threading;

namespace MonitorDemo
{
    class Program
    {
        static readonly object lockObject = new object();

        public static void PrintNumbers()
        {
            Console.WriteLine(Thread.CurrentThread.Name + " Trying to enter into the critical section");
            bool IsLockTaken = false;

            Monitor.Enter(lockObject, ref IsLockTaken);
            try
            {
                Console.WriteLine(Thread.CurrentThread.Name + " Entered into the critical section");
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(100);
                    Console.Write(i + ",");
                }
                Console.WriteLine();
            }
            finally
            {
                if(IsLockTaken)
                {
                    Monitor.Exit(lockObject);
                }
                Console.WriteLine(Thread.CurrentThread.Name + " Exit from critical section");
            }
        }

        static void Main(string[] args)
        {
            Thread[] Threads = new Thread[3];
            for (int i = 0; i < 3; i++)
            {
                Threads[i] = new Thread(PrintNumbers);
                Threads[i].Name = "Child Thread " + i;
            }

            foreach (Thread t in Threads)
            {
                t.Start();
            }

            Console.ReadLine();
        }
    }
}
Output:

How to Protect the Shared Resources in Multithreading using Monitor Class from Concurrent Access in C# with Examples

Example to Understand TryEnter(Object, TimeSpan, Boolean) Method of Monitor Class in C#:

This method attempts, for the specified amount of time, to acquire an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken. The syntax is given below to use the TryEnter(Object, TimeSpan, Boolean) Method of Monitor Class in C#.

TryEnter(Object, TimeSpan, Boolean) Method of Monitor Class in C#

Let us understand how to use TryEnter(Object, TimeSpan, Boolean) Method of Monitor Class in C# with an example. For a better understanding, please have a look at the below example.

using System;
using System.Threading;

namespace MonitorDemo
{
    class Program
    {
        static readonly object lockObject = new object();

        public static void PrintNumbers()
        {
            var lockObj = new object();
            var timeout = TimeSpan.FromMilliseconds(500);
            bool lockTaken = false;

            try
            {
                Console.WriteLine(Thread.CurrentThread.Name + " Trying to enter into the critical section");
                Monitor.TryEnter(lockObj, timeout, ref lockTaken);
                if (lockTaken)
                {
                    Console.WriteLine(Thread.CurrentThread.Name + " Entered into the critical section");
                    for (int i = 0; i < 5; i++)
                    {
                        Thread.Sleep(100);
                        Console.Write(i + ",");
                    }
                    Console.WriteLine();
                }
                else
                {
                    // The lock was not acquired.
                    Console.WriteLine(Thread.CurrentThread.Name + " Lock was not acquired");
                }
            }
            finally
            {
                // Ensure that the lock is released.
                if (lockTaken)
                {
                    Monitor.Exit(lockObj);
                }
                Console.WriteLine(Thread.CurrentThread.Name + " Exit from critical section");
            }
        }

        static void Main(string[] args)
        {
            Thread[] Threads = new Thread[3];
            for (int i = 0; i < 3; i++)
            {
                Threads[i] = new Thread(PrintNumbers);
                Threads[i].Name = "Child Thread " + i;
            }

            foreach (Thread t in Threads)
            {
                t.Start();
            }

            Console.ReadLine();
        }
    }
}

Output:

Example to Understand TryEnter(Object, TimeSpan, Boolean) Method of Monitor Class in C#

Example to Understand Wait() and Pulse() Methods of Monitor Class in C#:

The Wait() Method of Monitor Class is used to Release the lock on an object in order to permit other threads to lock and access the object. The calling thread waits while another thread accesses the object. The Pulse signals are used to notify waiting threads about changes to an object’s state. For a better understanding, please have a look at the below example.

Let us understand this with a real-time example. Our business requirement is to print the Even and Odd number sequence using 2 different threads. So, One thread will print the even numbers and another thread will print the odd numbers.
Thread T1 : 0,2,4,6,8…
Thread T2 :1,3,5,7,9…
Output: 0,1,2,3,4,5,6,7,8,9…

To solve the problem let’s use the signaling mechanism using the Monitor Class Wait() and Pulse() Methods in C#. In the following example, we use the Monitor.Wait() method to make the thread waiting and Monitor.Pulse() method to signal other thread. The process is as follows:

  1. First, the Even thread will start to print the number on the console.
  2. Then the Even thread will signal the Odd thread to print the number using the Monitor.Pulse() method.
  3. Again, the Even thread will wait for a signal from the Odd thread using Monitor.Wait() method.
  4. The same thing will also be done by the Odd Thread.
  5. The Odd thread will start to print the number on the console.
  6. Then the Odd thread will signal the Even thread to print the number using Monitor.Pulse() method.
  7. Again, the Odd thread will wait for the signal from the Even thread using Monitor.Wait() method.
  8. The same process is going on.

Since both the Odd and Even threads are sharing the same console window to print the number we need to put a lock on the console IO. We want the sequence to be started with the even number, So, Even thread must run first. Once, we will start the Even thread, then we need to pause for a moment before starting the Odd thread using the Sleep() method of the Thread class in C# to avoid any chance to start the Odd thread first.

using System;
using System.Threading;

namespace odd_even_sequence
{
    class Program
    {
        // upto the limit numbers will be printed on the Console
        const int numberLimit = 20;

        static readonly object _lockMonitor = new object();

        static void Main(string[] args)
        {
            Thread EvenThread = new Thread(PrintEvenNumbers);
            Thread OddThread = new Thread(PrintOddNumbers);

            //First Start the Even thread.
            EvenThread.Start();

            //Puase for 10 ms, to make sure Even thread has started 
            //or else Odd thread may start first resulting different sequence.
            Thread.Sleep(100);

            //Next, Start the Odd thread.
            OddThread.Start();

            //Wait for all the childs threads to complete
            OddThread.Join();
            EvenThread.Join();

            Console.WriteLine("\nMain method completed");
            Console.ReadKey();
        }

        //Printing of Even Numbers Function
        static void PrintEvenNumbers()
        {
            try
            {
                //Implement lock as the Console is shared between two threads
                Monitor.Enter(_lockMonitor);
                for (int i = 0; i <= numberLimit; i = i + 2)
                {
                    //Printing Even Number on Console)
                    Console.Write($"{i} ");

                    //Notify Odd thread that I'm done, you do your job
                    Monitor.Pulse(_lockMonitor);

                    //I will wait here till Odd thread notify me 
                    // Monitor.Wait(monitor);
                    //Without this logic application will wait forever
                    
                    bool isLast = false;
                    if (i == numberLimit)
                    {
                        isLast = true;
                    }

                    if (!isLast)
                    {
                        //I will wait here till Odd thread notify me
                        Monitor.Wait(_lockMonitor);
                    }
                }
            }
            finally
            {
                //Release the lock
                Monitor.Exit(_lockMonitor);
            }

        }

        //Printing of Odd Numbers Function
        static void PrintOddNumbers()
        {
            try
            {
                //Hold lock as the Console is shared between two threads
                Monitor.Enter(_lockMonitor);
                for (int i = 1; i <= numberLimit; i = i + 2)
                {
                    //Printing the odd numbers on the console
                    Console.Write($"{i} ");

                    //Notify Even thread that I'm done, you do your job
                    Monitor.Pulse(_lockMonitor);

                    //I will wait here till even thread notify me
                    // Monitor.Wait(monitor);
                    // without this logic application will wait forever

                    bool isLast = false;
                    if (i == numberLimit - 1)
                    {
                        isLast = true;
                    }

                    if (!isLast)
                    {
                        //I will wait here till Even thread notify me
                        Monitor.Wait(_lockMonitor);
                    }
                }
            }
            finally
            {
                //Release lock
                Monitor.Exit(_lockMonitor);
            }
        }
    }
}
Output:

Example to Understand Wait() and Pulse() Methods of Monitor Class in C#

Difference Between Monitor and Lock in C#

The Difference between monitor and lock in C# is that lock internally wraps the Enter and Exit methods in a try…finally blocks with exception handling. Whereas for Monitor class in C#, we use try and finally block explicitly to release lock properly. So, Lock = Monitor + try-finally.

The lock provides the basic functionality to acquire an exclusive lock on a synchronized object. But, If you want more control to implement advanced multithreading solutions using TryEnter(), Wait(), Pulse(), and PulseAll() methods, then the Monitor class is your option.

Limitations of Locks and Monitors in C#:

Locks and Monitors help us to ensure that our code is thread-safe. That means when we run our code in a multi-threaded environment then we don’t end up with inconsistent results. For a better understanding, please have a look at the below image.

Limitations of Lock and Monitor in C#

But there are some limitations to locks and monitors. The locks and monitors ensure thread safety for threads that are In-Process i.e. the threads that are generated by the application itself i.e. Internal Threads. But if the threads are coming from external applications (Out-Process) or External Threads then Locks and Monitors have no control over them. So, in a situation like this, we need to use Mutex. In our next article, we will discuss Mutex.

Here, in this article, I try to explain How to Protect the Shared Resources in Multithreading using the Monitor class from Concurrent Access in C# with Examples. In the next article, I am going to discuss the Mutex in C# with Examples. I hope you enjoy this How to Protect the Shared Resources in Multithreading using the Monitor class from Concurrent Access in C# with Examples article.

3 thoughts on “Monitor Class in C#”

  1. blank

    Hi,
    Thanks for this article, very good explained.

    Can you show also an example of using the special method of Monitor: TryEnter() Wait(), Pulse(), & PulseAll() ?
    Thanks.

  2. blank

    Regarding the Monitor.EnterlockObject, ref IslockTaken) example:
    I think you forgot this statement – if (lockTaken) – before the Monitor.Exit.

Leave a Reply

Your email address will not be published.