I was speaking with a colleague earlier today about a potential threading issue in his code. I suggested serializing access to his object to ensure two threads are not using it at the same time, as that could corrupt the processing. Being a relative “old-timer” who’s largest mutithreading applications were written in Visual C++ v 5 and 6, I remember heavy use of the CriticalSection, and wrapping the lock/unlock of critical sections in a class to ensure that every lock eventually got unlocked. We developed the class to ensure matched Enters and Exits after many hours of debugging deadlocks in the early development stages. But I digress a little… .NET has wrapped it all up for us now, and I thought I’d just right this up for the next time someone asks me about it.
The Monitor class (in System.Threading) can help synchronize access to a particular object or resource, ensuring that once a thread has access, it will remain the only thread with access until the lock is released. To use it, create an object that is shared with all the threads that will be accessing the resource (perhaps a static member of a class they all use). It doesn’t have to be any particular type of object. As a matter of fact, it could be declared and initialized (in C#) as:
Object oLocker = new Object();
Now if you want exclusive access to the resource, you can use the Monitor class like this:
Monitor.Enter(oLocker);
If another thread has already “entered” for this object, your thread will block until that other thread calls Monitor.Exit(oLocker). Many threads can be blocked at once. When the owning thread calls the Monitor.Exit(), one of those waiting threads is allowed access. I believe it is FIFO, but I don’t know if that is guaranteed.
A thread can actually end up calling Monitor.Enter() multiple times on the same object. If the thread already owns the lock, then this will simply increment a lock count. You MUST call Exit for each time you call Enter. Otherwise you WILL cause a deadlock in your program.
You could make sure you call Exit yourself, by using the try / finally construct. You would Call Monitor.Enter() in the try block, and call Monitor.Exit() in the finally block. So even if your code throws an unhandled exception, the finally code block would exit the lock.
The C# and VB.NET languages have macros to help you ensure you unlock at the end. C# uses lock, VB.Net uses SyncLock.
In C# you would use lock like this:
…
lock(oLocker)
{
<put your synchronized code here>
}
What lock does is call Monitor.Enter() on the object in the parenthesis, and add a finally block for you at the end that calls Monitor.Exit(). This makes your code easier to read and easier to write.
SyncLock does basically the same thing in VB.NET. The syntax is slightly different.
Here is a VB.Net example:
SyncLock oLocker
<put your synchronized code here>
End SyncLock
Any code that uses lock or SyncLock (or Monitor.Enter()/Monitor.Exit()) on a particular object can run with the assurance that no other thread will interfere with it.