Wednesday, March 24, 2010

Basics about Monitor in .NET

The Monitor class controls access to objects by granting a lock for an object to a single thread. Object locks provide the ability to restrict access to a block of code, commonly called a critical section. While a thread owns the lock for an object, no other thread can acquire that lock.

Psudo code for synchronizing access to critical section:
------------------------------------------
morCodeLock = new object();

Monitor.Enter(morCodeLock);

//ender critical section
try
{
//do some work
under condition1
Monitor.Wait(morCodeLock) // cannot continue, put to waiting queue
//do some work
under condition2
Monitor.Pulse(morCodeLock) //ready to run, put to ready queue
}
finally
{
Monitor.Exit(morCodeLock);
}
//leave critical section
-----------------------------------------------

The synchronization object "morCodeLock" maintains the following information:
1. A reference to the thread that currently holds the lock.
2. A reference to a ready queue, which contains the threads that are ready to obtain the lock.
3. A reference to a waiting queue, which contains the threads that are waiting for notification of a change in the state of the object morCodeLoc.

The Pulse, PulseAll, and Wait methods must be invoked from within a synchronized block of code. These methods plus ready and waiting queues allow Monitor to have more control over synchronized code section which can be useful in producer-consumer and other scenarios. The executing thread can decide when to put itself into waiting queue and moved other thread(s) from the waiting queue to the ready queue,

Monitor.Enter(morCodeLock):
The call will lock morCodeLock object if it is not locked and the calling thread enters the critical region, otherwise (morCodeLock is locked already) the calling thread will be put in the ready queue.

Monitor.Wait(morCodeLock):
The thread that currently owns the lock on the specified object invokes this method in order to release the object so that another thread can access it. The caller is blocked while waiting to reacquire the lock. This method is called when the caller needs to wait for a state change that will occur as a result of another thread's
operations. When a thread calls Wait, it releases the lock on the object and enters the object's waiting queue. The next thread in the object's ready queue (if there is one) acquires the lock and has exclusive use of the object. All threads that call Wait remain in the waiting queue until they receive a signal from Pulse or
PulseAll, sent by the owner of the lock. If Pulse is sent, only the thread at the head of the waiting queue is affected. If PulseAll is sent, all threads that are waiting for the object are affected. When the signal is received, one or more threads leave the waiting queue and enter the ready queue. A thread in the ready queue is permitted to reacquire the lock. This method returns when the calling thread reacquires the lock on the object. Note that this method blocks indefinitely if the holder of the lock does not call Pulse or PulseAll.

Monitor.Pulse(morCodeLock):
The thread that currently owns the lock on morCodeLock invokes this method to signal the next thread in line for the lock. Upon receiving the pulse, the waiting thread is moved to the ready queue. When the thread that invoked Pulse releases the lock, the next thread (the head) in the ready queue (which is not necessarily the thread that was pulsed) acquires the lock. To signal multiple threas, use the PulseAll method.

More details:
The Monitor class does not maintain state indicating that the Pulse method has been called. Thus, if you call Pulse when no threads are waiting, the next thread that calls Wait blocks as if Pulse had never been called. If two threads are using Pulse and Wait to interact, this could result in a deadlock. Contrast this with the behavior of the AutoResetEvent class: If you signal an AutoResetEvent by calling its Set method, and there are no threads waiting, the AutoResetEvent remains in a signaled state until a thread calls WaitOne, WaitAny, or WaitAll. The AutoResetEvent releases that thread and returns to the unsignaled state.

Monitor vs Waithandle:
It is important to note the distinction between use of Monitor and WaitHandle objects. Monitor objects are purely managed, fully portable, and might be more efficient in terms of operating-system resource requirements. WaitHandle objects represent operating-system waitable objects, are useful for synchronizing between managed and unmanaged code, and expose some advanced operating-system features like the ability to wait on many objects at once.

Code Sample:
-------------------------------------------
class TestMonitor
{
object morCodeLock = new object();

public void Test()
{
for (int i = 0; i < 5; i++)
{
ThreadPool.QueueUserWorkItem(this.TestCriticalRegion, i);
}
}

void TestCriticalRegion(object o)
{
int i = (int)o;
Console.WriteLine("thread " + i.ToString() + " Enter method");

Monitor.Enter(morCodeLock);
try
{
if (i == 0)
Monitor.Wait(morCodeLock);
Console.WriteLine("thread " + i.ToString() + " start critical");
Thread.Sleep(5000);
Console.WriteLine("thread " + i.ToString() + " end critical");
if (i == 4)
Monitor.Pulse(morCodeLock);
}
finally
{
Monitor.Exit(morCodeLock);
}
Console.WriteLine("thread " + i.ToString() + " exist method");
}
}
-------------------------------------------
Output:
thread 0 Enter method
thread 1 Enter method
thread 1 start critical
thread 2 Enter method
thread 3 Enter method
thread 4 Enter method
thread 1 end critical
thread 1 exist method
thread 2 start critical
thread 2 end critical
thread 2 exist method
thread 3 start critical
thread 3 end critical
thread 4 start critical
thread 3 exist method
thread 4 end critical
thread 4 exist method
thread 0 start critical
thread 0 end critical
thread 0 exist method