C# Source Code: Using a timed lock to prevent deadlocks
[
Home
|
Contents
|
Search
|
Reply
| Previous | Next ]
C# Source Code
Using a timed lock to prevent deadlocks
By:
Andrew Baker
Email (spam proof):
Email the originator of this post
Date:
Tuesday, April 19, 2005
Hits:
3270
Category:
Threading/Asynchronous operations
Article:
The lock keyword in C# was designed to make writing thread-safe code easier. Unfortunately it has one key oversight, it doesn't allow you to specify how long to wait for a lock. Because of this limitation it is not advisable to use the "lock" statement in production code. Note, deadlocks occur when two or more threads of control are blocked waiting on each other's forward progress. If you reflect over the code generated by the lock keyword you will see the following: lock (myObj) { // ... other code } gets compiled into this: Monitor.Enter(myObj); try { ... do something with shared state ... } finally { Monitor.Exit(obj); } The code below shows how to create thread safe code by replacing for the "lock" keyword with the TimedLock class. The TimedLock class has one key difference, it takes a timespan allowing you to configure how long to wait for a lock. If this timeout expires the TimedLock will throw a TimedLockException. See the console app demonstration code below for more details. using System; using System.Threading; ///
/// A class demonstrating how to use timed locks. ///
class TimedLockDemo { ///
/// Private resource, used to demonstrate the timed lock. ///
private static object _sharedLock = new object(); private static bool _testSuccessful = false; private static bool _thread1Proceed = false; private static bool _thread2Proceed = false; ///
/// The main entry point for the application. ///
[STAThread] static void Main(string[] args) { int counter = 0; Console.WriteLine("Starting test..."); // Create and start the threads Thread thread1 = new Thread(new ThreadStart(_TestDeadlockThread1)); Thread thread2 = new Thread(new ThreadStart(_TestDeadlockThread2)); thread1.Start(); thread2.Start(); do { // Wait for thread 2 to mark the flag Thread.Sleep(500 ); counter++; if ( counter > 10 ) { Console.WriteLine("Error: Test timed out!"); } } while ( !_testSuccessful ); Console.ReadLine(); } #region Private Methods ///
/// Called by Thread 1. Simulates a deadlock. ///
private static void _TestDeadlockThread1() { int counter = 0; Console.WriteLine("Thread 1 Started."); // Grab the lock Console.WriteLine("Thread 1 trying to grab lock."); using (TimedLock.Lock( _sharedLock, new TimeSpan(0,0,5) )) { Console.WriteLine("Thread 1 grabbed lock."); // Let thread 2 proceed _thread2Proceed = true; do { // Wait for thread 2 to mark the flag Thread.Sleep( 500 ); Console.WriteLine("Thread 1 waiting for proceed signal from thread 2."); counter++; if ( counter > 6 ) { Console.WriteLine("Error: Thread 2 should have already thrown an exception and set the proceed flag to true."); } } while ( !_thread1Proceed ); } _testSuccessful = true; Console.WriteLine("Test successfully completed..."); } ///
/// Called by Thread 2. Simulates a deadlock. ///
private static void _TestDeadlockThread2() { // Wait for thread 1 to mark the flag Console.WriteLine("Thread 2 Started."); do { Console.WriteLine("Thread 2 waiting for proceed signal from thread 1."); Thread.Sleep(500 ); } while ( !_thread2Proceed ); try { // Try and grab the lock (waiting for 2 secs) Console.WriteLine("Thread 2 trying to grab lock."); using (TimedLock.Lock( _sharedLock, new TimeSpan(0,0,1) )) { Console.WriteLine("Thread 2 grabbed lock!"); Console.WriteLine("Error: Managed to obtain the same lock in two different threads!"); } } catch( TimedLockException ) { // As expected we timed out while trying to grab the same lock as thread 1. // So now signal thread 1 to proceed. Console.WriteLine("Thread 2 timed out while trying to grab the lock."); _thread1Proceed = true; } } #endregion } #region TimedLock ///
/// A resource locking class which contains built in deadlock handling. /// Use in preference to using the lock statemet ("lock( myObject)") as the /// TimedLock object has built in deadlock handling. ///
///
Intented use: /// // Place lock on resource /// using( TimedLock.Lock( myObject )) /// { /// // .. do stuff /// } ///
public struct TimedLock: IDisposable { #region Private Variables ///
/// The object being locked. ///
private object _lockObject; private static TimeSpan _defaultTimeOut = TimeSpan.FromSeconds(20); #endregion Private Variables #region Constructors ///
/// Private constructor to create a new TimeLock. ///
///
The object to lock. private TimedLock (object lockObject) { _lockObject = lockObject; #if DEBUG _undisposedLockDetector = new UndisposedLockDetector( lockObject ); #endif } #endregion Constructors #region Public Properties ///
/// Gets or sets the default time to wait for the lock on the specified object. ///
public static TimeSpan DefaultTimeOut { get{ return _defaultTimeOut; } set{ _defaultTimeOut = value; } } #endregion Public Properties #region Public Methods ///
/// Applies a lock to the specified resource (using the default timeout
DefaultTimeOut
).. ///
///
The resource to lock. ///
Returns the lock.
///
Thrown when the DefaultTimeOut is exceeded.
///
Intented use: /// // Place lock on resource /// using( TimedLock.Lock ( myObject )) /// { /// // .. do stuff /// } ///
public static IDisposable Lock (object lockObject) { return Lock( lockObject, DefaultTimeOut, null ); } ///
/// Applies a lock to the specified resource (using the default timeout
DefaultTimeOut
).. ///
///
The resource to lock. ///
Information to add to the error message if the lock times out (nullable). ///
Returns the lock (must be Disposed of).
///
Thrown when the DefaultTimeOut is exceeded.
///
Intented use: /// // Place lock on resource /// using( TimedLock.Lock ( myObject )) /// { /// // .. do stuff /// } ///
public static IDisposable Lock (object lockObject, string timeoutErrorMessage) { return Lock( lockObject, DefaultTimeOut, timeoutErrorMessage ); } ///
/// Applies a lock to the specified resource (using the specified timeout).. ///
///
The resource to lock. ///
The deadlock timeout ///
Returns the lock.
///
Thrown when the
DefaultTimeOut
is exceeded.
///
Intented use: /// using( TimedLock.Lock ( myObject )) /// { /// // .. do stuff /// } ///
public static IDisposable Lock(object lockObject, TimeSpan timeout) { return Lock( lockObject, timeout, null ); } ///
/// Applies a lock to the specified resource (using the specified timeout).. ///
///
The resource to lock. ///
The deadlock timeout ///
Information to add to the error message if the lock times out (nullable). ///
Returns the lock.
///
Thrown when the
DefaultTimeOut
is exceeded.
///
Intented use: /// using( TimedLock.Lock ( myObject )) /// { /// // .. do stuff /// } ///
public static IDisposable Lock(object lockObject, TimeSpan timeout, string timeoutErrorMessage) { // Check parameters if ( lockObject == null ) { // Throw an exception throw new ArgumentNullException("lockObject", "Please specify a lockObject."); } // Create new lock TimedLock timedLock = new TimedLock ( lockObject ); // Try a lock on the resource if (!Monitor.TryEnter (lockObject, timeout)) { #if DEBUG // Failed to get the lock, so prevent the finalizer from being called System.GC.SuppressFinalize(timedLock._undisposedLockDetector); #endif // Throw new timeout exception string message = "Lock timeout expired!"; if ( timeoutErrorMessage != null ) { message += Environment.NewLine + timeoutErrorMessage; } throw new TimedLockException( message ); } return timedLock; } #endregion Public Methods #region IDisposable Members ///
/// Disposes of the timed lock resource (MUST BE CALLED - USE USING STATEMENTS). ///
public void Dispose() { Monitor.Exit ( _lockObject ); _lockObject = null; // It's a bad error if someone forgets to call Dispose, // so in Debug builds, we put a finalizer in to detect // the error. If Dispose is called, we suppress the // finalizer. #if DEBUG // Prevent finalizer from being called. GC.SuppressFinalize(_undisposedLockDetector); #endif } #endregion #region [Debug] UndisposedLockDetector Class and Variables #if DEBUG ///
/// Debug object to detect any undisposed locks. ///
private UndisposedLockDetector _undisposedLockDetector; #region UndisposedLockDetector (Private Class) /// Debug object to detect and warn developers of any undisposed locks. private class UndisposedLockDetector { #region Private Variables private object _lockObject = null; #endregion Private Variables #region Constructors ///
/// Default constructor. ///
///
public UndisposedLockDetector(object lockObject) { _lockObject = lockObject; } #endregion Constructors #region Finalizer ///
/// Finalizer. This should not get called if the object has been correctly disposed of. ///
~UndisposedLockDetector() { string message; // If this finalizer runs, someone somewhere failed to // call Dispose, which means we've failed to leave // a monitor! if ( _lockObject != null ) { message = "Error. Undisposed TimedLock on object: " + Environment.NewLine + _lockObject.ToString() ; } else { message = "Error. Undisposed TimedLock on a null object."; } Console.WriteLine( message ); System.Diagnostics.Debug.Fail( message ); } #endregion Finalizer } #endregion UndisposedLockDetector (Private Class) #endif #endregion [Debug] UndisposedLockDetector Class and Variables } #endregion #region TimedLockException ///
/// Exception thrown when the
TimedLock
lock expires while waiting for a lock on a resource. ///
[Serializable] public class TimedLockException : ApplicationException { ///
/// Default constructor. ///
///
The lock exception message. public TimedLockException(string message) :base( message ) { } } #endregion
Terms and Conditions
Support this site
Download a trial version of the best FTP application on the internet