Back to: C#.NET Tutorials For Beginners and Professionals
Monitor Class in C# with Examples
In this article, I am going to discuss How to Protect Shared Resources in Multithreading using Monitor Class in C# with Examples. Please read our previous article where we discussed How to Protect shared Resources using Lock in C# from Concurrent Access with Examples. As part of this article, we are going to discuss the following pointers.
- Understanding the Monitor Class in C#
- Example to understand Monitor Class in C# to Protect Shared Resource from Concurrent Access
- How does the Monitor Class work in C#?
- Example to Understand Monitor.Enter(lockObject, ref IslockTaken) Method in C#
- 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#
- Difference Between Monitor and Lock in C#
- Limitations of Locks and Monitors in C#
We have already discussed that 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 have seen how to use the lock mechanism to achieve thread safety in a multi-thread environment. Now, let us proceed and try to 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.
Understanding the Monitor Class in C#:
According to Microsoft, 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 class 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 synchronized access to the critical section associated with a particular object.
Let us understand the Role and Responsibilities of each method of the Monitor class. Following is the list of important methods of Monitor class.
- Enter(): When we invoke the Enter method of the Monitor class, it acquires an exclusive lock on the specified object. This also marks the beginning of a critical section or the beginning of a shared resource.
- Exit(): When the Exit method is invoked of the Monitor class, then it releases the lock on the specified object. This also marks the end of a critical section or the end of the shared resource protected by the locked object.
- Pules(): When the Pulse method is invoked of the Monitor class, it sends a signal to a thread in the waiting queue of a change in the locked object’s state.
- Wait(): When the Wait method is invoked of the Monitor class, then it releases the lock on an object and blocks the current thread until it reacquires the lock.
- PulesAll(): When the PulseAll method is invoked of the Monitor class, it sends signals to all waiting threads of a change in the locked object’s state.
- TryEnter(): When we invoke the TryEnter method of the Monitor class, it attempts to acquire an exclusive lock on the specified object.
Note: If this is not clear at the moment, then don’t worry we will try to understand all the above methods with Examples and you also see that there are many overloaded versions of the above methods are also available.
Example to understand Monitor Class in C# to Protect Shared Resource from Concurrent Access:
The following is the syntax to use the Enter and Exit methods 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 methods using the class name i.e. Monitor.
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. In the below example, we have one Shared Resource, and we are accessing that resource concurrently by using three different threads. Then we used the Monitor class Enter and Exit Methods to protect the critical section code. In this case, all three threads will try to acquire an exclusive lock, but at any given point in time, only one thread gets an exclusive lock and will enter into the critical section and all other threads will wait until the thread releases the lock.
using System; using System.Threading; namespace MonitorDemo { class Program { private static readonly object lockPrintNumbers = new object(); public static void PrintNumbers() { Console.WriteLine(Thread.CurrentThread.Name + " Trying to enter into the critical section"); try { Monitor.Enter(lockPrintNumbers); 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(lockPrintNumbers); 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) { Name = "Child Thread " + i }; } foreach (Thread t in Threads) { t.Start(); } Console.ReadLine(); } } }
Output:
How does the Monitor Class work in C#?
The Monitor class in C# provides a wait-based synchronization mechanism that allows only one thread to access the critical section code at a time to avoid the race condition. All the other threads have to wait and halt the execution until the locked object is released.
To understand how the Monitor class work in C#, please have a look at the following diagram. As shown in the below image, as soon as a thread execute the Enter method of the Thread class, it will be in the Ready Queue, and in the same way, many threads can be there in the Ready Queue. Then one of the threads from the Ready Queue will acquire an Exclusive Lock on the Object and will enter inside the Critical Section and execute the code, and at that time, no other threads can get a chance to enter into the critical section. And then when we execute the Exit Method of the Thread class, then the currently executing thread will move into the Waiting Queue and will send one signal to the threads which are there in the Ready queue, and one of the Threads from the Ready queue will acquire the lock and will enter the Critical section and starts executing the code of Critical Section. This is how the Monitor class works in C#.
Monitor.Enter(lockObject, ref IslockTaken) Method in C#:
Let us understand the other 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.
The following example shows how to use Enter(lockObject, ref IslockTaken) method of the Monitor class in C#. The following example is the same as the previous example except here we are using the overloaded version of the Enter method which takes two parameters. The second boolean parameter specifies whether the thread acquires a lock or not, true indicates that it acquires a lock on the object and false indicates that it does not acquire a lock on the object and again in the finally block we are checking the boolean value and accordingly we are releasing the lock.
using System; using System.Threading; namespace MonitorDemo { class Program { private static readonly object lockPrintNumberst = new object(); public static void PrintNumbers() { Console.WriteLine(Thread.CurrentThread.Name + " Trying to enter into the critical section"); bool IsLockTaken = false; try { Monitor.Enter(lockPrintNumberst, ref IsLockTaken); if(IsLockTaken) { 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(lockPrintNumberst); 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) { 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#:
This method attempts, for a specified amount of time, to acquire an exclusive lock on the specified object, and automatically sets a value that indicates whether the lock was taken or not. The syntax is given below to use the TryEnter(Object, TimeSpan, Boolean) Method of Monitor Class in C#.
For a better understanding, please have a look at the below example which shows how to use TryEnter(Object, TimeSpan, Boolean) Method of Monitor Class in C#. In the below example, we have specified the timeout as 1000 milliseconds or you can say 1 second. If within 1 second if the thread will not acquire the lock, then it will not enter the critical section.
using System; using System.Threading; namespace MonitorDemo { class Program { private static readonly object lockPrintNumbers = new object(); public static void PrintNumbers() { TimeSpan timeout = TimeSpan.FromMilliseconds(1000); bool lockTaken = false; try { Console.WriteLine(Thread.CurrentThread.Name + " Trying to enter into the critical section"); Monitor.TryEnter(lockPrintNumbers, 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 { // To Ensure that the lock is released. if (lockTaken) { Monitor.Exit(lockPrintNumbers); 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) { Name = "Child Thread " + i }; } foreach (Thread t in Threads) { t.Start(); } Console.ReadLine(); } } }
When you run the above code you will get the following output. The output may vary in your machine. As you can see, all three threads trying to acquire a lock on the object within 1 second, and out of three threads, two threads acquire an exclusive lock on the object while one thread is unable to acquire an exclusive lock, and hence that thread will not enter into the critical section.
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 a locked object’s state.
Let us understand this with one 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 above problem let us 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:
- First, the Even thread will start to print the number on the console.
- Then the Even thread will signal the Odd thread to be ready to print the number using the Monitor.Pulse() method.
- Then the Event thread will call the Monitor.Wait() method which will allow to current thread to block and allows the Odd thread to start execution.
- The same thing will also be done by the Odd Thread.
- The Odd thread will start to print the number on the console.
- Then the Odd thread will signal the Even thread to be ready to print the number using Monitor.Pulse() method.
- Then the Odd thread will call the Monitor.Wait() method which will allow to current thread to block and allows the Even thread to start execution.
- 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, the 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 //It notifies a thread in the waiting queue of a change in the //locked object's state. 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 //Releases the lock on an object and blocks the current thread //until it reacquires the lock. 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:
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 the Monitor class in C#, we need to use the try and finally block explicitly to release the 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.
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.
Monitor class Methods in Detail:
Let us understand the Role and Responsibilities of each method of the Monitor class according to Microsoft.
- 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:
- public static bool TryEnter(object obj, TimeSpan timeout): Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object.
- 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.
- 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.
- public static bool TryEnter(object obj): Attempts to acquire an exclusive lock on the specified object.
- public static bool TryEnter(object obj, int millisecondsTimeout): Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object.
- 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 used by the TryEnter method.
- 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.
- 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.
- 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.
- 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:
- public static bool Wait(object obj): It Releases the lock on an object and blocks the current thread until it reacquires the lock.
- 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.
- 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.
- 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.
- 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 is 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.
- object obj: The object on which to wait. It will throw ArgumentNullException if the obj parameter is null.
- 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.
- 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.
- bool exitContext: true to exit and reacquire the synchronization domain for the context (if in a synchronized context) before the wait; otherwise, false.
- 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 following 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.
- 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.
- 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.
- 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:
- 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.
In the next article, I am going to discuss the Mutex in C# with Examples. 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. 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.
About the Author: Pranaya Rout
Pranaya Rout has published more than 3,000 articles in his 11-year career. Pranaya Rout has very good experience with Microsoft Technologies, Including C#, VB, ASP.NET MVC, ASP.NET Web API, EF, EF Core, ADO.NET, LINQ, SQL Server, MYSQL, Oracle, ASP.NET Core, Cloud Computing, Microservices, Design Patterns and still learning new technologies.
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.
Hi,
As per your request, we have updated the content which includes the examples of TryEnter() Wait(), Pulse(), & PulseAll().
Regarding the Monitor.EnterlockObject, ref IslockTaken) example:
I think you forgot this statement – if (lockTaken) – before the Monitor.Exit.
Thanks for identifying the mistake. We have corrected this one.
The first output display result should be wrong. It should be that thread 2 exits first before thread 1 enters. The output result graph shows that thread 1 enters before thread 2 exits
How to handle await() and pulse() if we have more than two threads instead of two. How it knows which thread to wait and pulse.
Can you give me an example ..
Thanks for this article, very good explained.