Thread Safety under .Net
"In general-purpose software engineering practice, we have reached a point where one approach to concurrent programming dominates all others, namely, threads." (The Problem with Threads)
Way back in the .Net 1.1 release, circa 2003, Microsoft gave adventurous developers the ability to create multi-threaded applications. That ability brought with it the need for developers to do thread synchronization, avoidance of race conditions, dealing with deadlocks, and new approaches to debugging.
In an effort to make object synchronization easier Microsoft introduced the lock keyword, which enabled the developer to wrap some critical section of code in a mutual-exclusion. This is a nice piece of syntactic sugar which wraps calls to System.Threading.Monitor.Enter() and Exit() and makes it easier for developers to serialize thread access in a multi-threaded environment. However, as has been reported by numerous other bloggers (IanG on Tap, Dimitri Glazkov, Eric Gunnerson), it does not allow you to easily specify a timeout period which means the waiting thread is blocked indefinitely. This is not at all desirable since it is often better to abort the operation after a predetermined time period because of the possibility of deadlocks (thread #1 holds a lock on resource A and wants to acquire a lock on resource B, and thread #2 holds a lock on resource B and wants to acquire a lock on resource A).
So advance 5 or so years, and 3 iterations of the .Net framework - now at version 3.5 - and we still have no way to pass a timeout period to the lock keyword. You can of course achieve this by writing code shown here: Yet More Timed Locking by IanG
The concensus amongst several posters was to implement the solution as follows:
- using a custom written struct to offer the new functionality;
- using a struct instead of a class to avoid a heap allocation;
- to call Monitor.TryEnter() rather than Monitor.Enter();
- to use the Monitor class in preference to the ReaderWriteLock since it's cheaper to acquire locks this way;
- to use the Monitor class in preference to the WaitHandle since it's cheaper to acquire locks this way;
- to throw a custom exception if the timeout period passes before acquiring the lock;
- avoid using try/catch logic that suppresses exceptions occurring inside the protected, critical section of code since it's possible to leave the state in an inconsistent state after the lock is released if this is the case;
- implement IDisposable to ensure release of the acquired lock once the struct goes out of scope -rather than relying on C# destructors since they are actually finalizers run by the GC in a non-deterministic fashion; and
- ensure the IDisposable.Dispose() method is public to avoid having the compiler boxing the IDisposable interface before calling Dispose().
The net result here (no pun intended) is a fairly decent work around to a language limitation, but why, oh why, after so many years is that language limitation still present? Had Microsoft added an extra, optional timeout parameter to the lock keyword, or implemented an alternate trylock(obj, timeout, delegate) keyword this whole detour could have been avoided resulting in far cleaner code and developers having to make fewer knowledge leaps to get to the "correct" solution to implement object synchronization. That would have been even sweeter syntactic sugar indeed!
13 Jul 2008 Damien Wintour







