Monitors
Monitor objects expose the ability to synchronize access to a region of code by taking and releasing a lock on a particular object using the Monitor.Enter, Monitor.TryEnter, and Monitor.Exitmethods. Once you have a lock on a code region, you can use the Monitor.Wait, Monitor.Pulse, and Monitor.PulseAllmethods. Waitreleases the lock if it is held and waits to be notified. When Wait is notified, it returns and obtains the lock again. Both Pulse andPulseAll signal for the next thread in the wait queue to proceed.
The Visual Basic SyncLock and C# lock statements use Monitor.Enter to take the lock and Monitor.Exit to release it. The advantage of using the language statements is that everything in the lock or SyncLock block is included in a Try statement. The Try statement has a Finally block to guarantee that the lock is released.
Monitor locks objects (that is, reference types), not value types. While you can pass a value type to Enter and Exit , it is boxed separately for each call. Since each call creates a separate object, Enter never blocks, and the code it is supposedly protecting is not really synchronized. In addition, the object passed to Exit is different from the object passed to Enter, so Monitor throws SynchronizationLockException with the message "Object synchronization method was called from an unsynchronized block of code." The following example illustrates these problems.
Try
Dim x As Integer = 1
' The call to Enter() creates a generic synchronizing object for the value
' of x each time the code is executed, so that Enter never blocks.
Monitor.Enter(x)
Try
' Code that needs to be protected by the monitor.
Finally
' Always use Finally to ensure that you exit the Monitor.
' The call to Exit() will FAIL!!!
' The synchronizing object created for x in Exit() will be different
' than the object used in Enter(). SynchronizationLockException
' will be thrown.
Monitor.Exit(x)
End Try
Catch SyncEx As SynchronizationLockException
Console.WriteLine("A SynchronizationLockException occurred. Message:")
Console.WriteLine(SyncEx.Message)
End Try
try
{
int x = 1;
// The call to Enter() creates a generic synchronizing object for the value
// of x each time the code is executed, so that Enter never blocks.
Monitor.Enter(x);
try
{
// Code that needs to be protected by the monitor.
}
finally
{
// Always use Finally to ensure that you exit the Monitor.
// The call to Exit() will FAIL!!!
// The synchronizing object created for x in Exit() will be different
// than the object used in Enter(). SynchronizationLockException
// will be thrown.
Monitor.Exit(x);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine("A SynchronizationLockException occurred. Message:");
Console.WriteLine(SyncEx.Message);
}
try
{
int x = 1;
// The call to Enter() creates a generic synchronizing object for the value
// of x each time the code is executed, so that Enter never blocks.
Monitor::Enter(x);
try
{
// Code that needs to be protected by the monitor.
}
finally
{
// Always use Finally to ensure that you exit the Monitor.
// The call to Exit() will FAIL!!!
// The synchronizing object created for x in Exit() will be different
// than the object used in Enter(). SynchronizationLockException
// will be thrown.
Monitor::Exit(x);
}
}
catch (SynchronizationLockException^ SyncEx)
{
Console::WriteLine("A SynchronizationLockException occurred. Message:");
Console::WriteLine(SyncEx->Message);
}
Although you can box a value type variable before calling Enter and Exit, as shown in the following example, and pass the same boxed object to both methods, there is no advantage to doing this. Changes to the variable are not reflected in the boxed copy, and there is no way to change the value of the boxed copy.
Dim x As Integer = 1
Dim o As object = x
Monitor.Enter(o)
Try
' Code that needs to be protected by the monitor.
Finally
' Always use Finally to ensure that you exit the Monitor.
Monitor.Exit(o)
End Try
int x = 1;
object o = x;
Monitor.Enter(o);
try
{
// Code that needs to be protected by the monitor.
}
finally
{
// Always use Finally to ensure that you exit the Monitor.
Monitor.Exit(o);
}
int x = 1;
Object^ o = x;
Monitor::Enter(o);
try
{
// Code that needs to be protected by the monitor.
}
finally
{
// Always use Finally to ensure that you exit the Monitor.
Monitor::Exit(o);
}
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.
The following code example demonstrates the combined use of the Monitor class (implemented with the lock and SyncLock compiler statements), the Interlocked class, and the AutoResetEvent class.
Imports System
Imports System.Threading
' Note: The class whose internal public member is the synchronizing
' method is not public; none of the client code takes a lock on the
' Resource object.The member of the nonpublic class takes the lock on
' itself. Written this way, malicious code cannot take a lock on
' a public object.
Class SyncResource
Public Sub Access(threadNum As Int32)
' Uses Monitor class to enforce synchronization.
SyncLock Me
' Synchronized: Despite the next conditional, each thread
' waits on its predecessor.
If threadNum Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum)
Thread.Sleep(200)
Console.WriteLine("Stop Synched Resource access (Thread={0})", threadNum)
End SyncLock
End Sub
End Class
' Without the lock, the method is called in the order in which threads reach it.
Class UnSyncResource
Public Sub Access(threadNum As Int32)
' Does not use Monitor class to enforce synchronization.
' The next call throws the thread order.
If threadNum Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum)
Thread.Sleep(200)
Console.WriteLine("Stop UnSynched Resource access (Thread={0})", threadNum)
End Sub
End Class
Public Class App
Private Shared numAsyncOps As Int32 = 5
Private Shared asyncOpsAreDone As New AutoResetEvent(false)
Private Shared SyncRes As New SyncResource()
Private Shared UnSyncRes As New UnSyncResource()
Public Shared Sub Main()
For threadNum As Int32 = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource), threadNum)
Next threadNum
' Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne()
Console.WriteLine(vbTab + vbNewLine + "All synchronized operations have completed." + vbTab + vbNewLine)
' Reset the thread count for unsynchronized calls.
numAsyncOps = 5
For threadNum As Int32 = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource), threadNum)
Next threadNum
' Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne()
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.")
End Sub
' The callback method's signature MUST match that of a
' System.Threading.TimerCallback delegate (it takes an Object
' parameter and returns void).
Shared Sub SyncUpdateResource(state As Object)
' This calls the internal synchronized method, passing
' a thread number.
SyncRes.Access(CType(state, Int32))
' Count down the number of methods that the threads have called.
' This must be synchronized, however; you cannot know which thread
' will access the value **before** another thread's incremented
' value has been stored into the variable.
If Interlocked.Decrement(numAsyncOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
asyncOpsAreDone.Set()
End If
End Sub
' The callback method's signature MUST match that of a
' System.Threading.TimerCallback delegate (it takes an Object
' parameter and returns void).
Shared Sub UnSyncUpdateResource(state As Object)
' This calls the unsynchronized method, passing a thread number.
UnSyncRes.Access(CType(state, Int32))
' Count down the number of methods that the threads have called.
' This must be synchronized, however; you cannot know which thread
' will access the value **before** another thread's incremented
' value has been stored into the variable.
If Interlocked.Decrement(numAsyncOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
asyncOpsAreDone.Set()
End If
End Sub
End Class
using System;
using System.Threading;
// Note: The class whose internal public member is the synchronizing
// method is not public; none of the client code takes a lock on the
// Resource object.The member of the nonpublic class takes the lock on
// itself. Written this way, malicious code cannot take a lock on
// a public object.
class SyncResource
{
public void Access(Int32 threadNum)
{
// Uses Monitor class to enforce synchronization.
lock (this)
{
// Synchronized: Despite the next conditional, each thread
// waits on its predecessor.
if (threadNum % 2 == 0)
{
Thread.Sleep(2000);
}
Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum);
Thread.Sleep(200);
Console.WriteLine("Stop Synched Resource access (Thread={0})", threadNum);
}
}
}
// Without the lock, the method is called in the order in which threads reach it.
class UnSyncResource
{
public void Access(Int32 threadNum)
{
// Does not use Monitor class to enforce synchronization.
// The next call throws the thread order.
if (threadNum % 2 == 0)
{
Thread.Sleep(2000);
}
Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum);
Thread.Sleep(200);
Console.WriteLine("Stop UnSynched Resource access (Thread={0})", threadNum);
}
}
public class App
{
static Int32 numAsyncOps = 5;
static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);
static SyncResource SyncRes = new SyncResource();
static UnSyncResource UnSyncRes = new UnSyncResource();
public static void Main()
{
for (Int32 threadNum = 0; threadNum < 5; threadNum+)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource), threadNum);
}
// Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\t\n");
// Reset the thread count for unsynchronized calls.
numAsyncOps = 5;
for (Int32 threadNum = 0; threadNum < 5; threadNum+)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource), threadNum);
}
// Wait until this WaitHandle is signaled.
asyncOpsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.");
}
// The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an Object
// parameter and returns void).
static void SyncUpdateResource(Object state)
{
// This calls the internal synchronized method, passing
// a thread number.
SyncRes.Access((Int32) state);
// Count down the number of methods that the threads have called.
// This must be synchronized, however; you cannot know which thread
// will access the value **before** another thread's incremented
// value has been stored into the variable.
if (Interlocked.Decrement(ref numAsyncOps) == 0)
{
// Announce to Main that in fact all thread calls are done.
asyncOpsAreDone.Set();
}
}
// The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an Object
// parameter and returns void).
static void UnSyncUpdateResource(Object state)
{
// This calls the unsynchronized method, passing a thread number.
UnSyncRes.Access((Int32) state);
// Count down the number of methods that the threads have called.
// This must be synchronized, however; you cannot know which thread
// will access the value **before** another thread's incremented
// value has been stored into the variable.
if (Interlocked.Decrement(ref numAsyncOps) == 0)
{
// Announce to Main that in fact all thread calls are done.
asyncOpsAreDone.Set();
}
}
}
#using <System.dll>
using namespace System;
using namespace System::Threading;
// Note: The class whose internal public member is the synchronizing
// method is not public; none of the client code takes a lock on the
// Resource object.The member of the nonpublic class takes the lock on
// itself. Written this way, malicious code cannot take a lock on
// a public object.
ref class SyncResource
{
public:
void Access(Int32 threadNum)
{
// Uses Monitor class to enforce synchronization.
Monitor::Enter(this);
try
{
// Synchronized: Despite the next conditional, each thread
// waits on its predecessor.
if (threadNum % 2 == 0)
{
Thread::Sleep(2000);
}
Console::WriteLine("Start Synched Resource access (Thread={0})", threadNum);
Thread::Sleep(200);
Console::WriteLine("Stop Synched Resource access (Thread={0})", threadNum);
}
finally
{
Monitor::Exit(this);
}
}
};
// Without the lock, the method is called in the order in which threads reach it.
ref class UnSyncResource
{
public:
void Access(Int32 threadNum)
{
// Does not use Monitor class to enforce synchronization.
// The next call throws the thread order.
if (threadNum % 2 == 0)
{
Thread::Sleep(2000);
}
Console::WriteLine("Start UnSynched Resource access (Thread={0})", threadNum);
Thread::Sleep(200);
Console::WriteLine("Stop UnSynched Resource access (Thread={0})", threadNum);
}
};
public ref class App
{
private:
static Int32 numAsyncOps = 5;
static AutoResetEvent^ asyncOpsAreDone = gcnew AutoResetEvent(false);
static SyncResource^ SyncRes = gcnew SyncResource();
static UnSyncResource^ UnSyncRes = gcnew UnSyncResource();
public:
static void Main()
{
for (Int32 threadNum = 0; threadNum < 5; threadNum+)
{
ThreadPool::QueueUserWorkItem(gcnew WaitCallback(SyncUpdateResource), threadNum);
}
// Wait until this WaitHandle is signaled.
asyncOpsAreDone->WaitOne();
Console::WriteLine("\t\nAll synchronized operations have completed.\t\n");
// Reset the thread count for unsynchronized calls.
numAsyncOps = 5;
for (Int32 threadNum = 0; threadNum < 5; threadNum+)
{
ThreadPool::QueueUserWorkItem(gcnew WaitCallback(UnSyncUpdateResource), threadNum);
}
// Wait until this WaitHandle is signaled.
asyncOpsAreDone->WaitOne();
Console::WriteLine("\t\nAll unsynchronized thread operations have completed.");
}
// The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an Object
// parameter and returns void).
static void SyncUpdateResource(Object^ state)
{
// This calls the internal synchronized method, passing
// a thread number.
SyncRes->Access((Int32) state);
// Count down the number of methods that the threads have called.
// This must be synchronized, however; you cannot know which thread
// will access the value **before** another thread's incremented
// value has been stored into the variable.
if (Interlocked::Decrement(numAsyncOps) == 0)
{
// Announce to Main that in fact all thread calls are done.
asyncOpsAreDone->Set();
}
}
// The callback method's signature MUST match that of a
// System.Threading.TimerCallback delegate (it takes an Object
// parameter and returns void).
static void UnSyncUpdateResource(Object^ state)
{
// This calls the unsynchronized method, passing a thread number.
UnSyncRes->Access((Int32) state);
// Count down the number of methods that the threads have called.
// This must be synchronized, however; you cannot know which thread
// will access the value **before** another thread's incremented
// value has been stored into the variable.
if (Interlocked::Decrement(numAsyncOps) == 0)
{
// Announce to Main that in fact all thread calls are done.
asyncOpsAreDone->Set();
}
}
};
int main()
{
App::Main();
}
See Also
Reference
Other Resources
Threading Objects and Features
Change History
Date |
History |
Reason |
---|---|---|
July 2008 |
Added clarification: SyncLock and lock statements use Monitor.Enter and Exit. |
Customer feedback. |