The lock keyword in C# is used to synchronize threads and to allow a simple way to write thread-safe code. It has a really simple usage:
lock (syncObject)
{
//do something
}
Behind the scenes the compiler will convert the code into:
Monitor.Enter(syncObject);
try
{
//do something
}
finally
{
Monitor.Exit(syncObject);
}
Having said that, the lock keyword can be dangerous because it can lead to deadlocks. In this post I will supply a small solution to this problem: "Lock with timeouts".
Consider this code:
if (!Monitor.TryEnter(syncObject, TimeSpan.FromSeconds(5))
{
throw new ApplicationException("Timeout waiting for lock");
}
try
{
//do something
}
finally
{
Monitor.Exit(syncObject);
}
As you can see with this code I use Monitor.TryEnter and not the Monitor.Enter. That means that if within the given time-span I will not acquire a lock, I will through an exception and will prevent a deadlock possibility. If the compiler could generate this code from the lock keyword we could have use it for "Timed Lock". However, this is not the case.
This solution will insure that you will never have a deadlock that is caused by the lock keyword.
The TimedLock struct
The solution is based on Ian Griffiths post. To sum things up for you, here is what you need to do in order to implement TimedLock:
using System;
using System.Threading;
#if DEBUG
public class TimedLock : IDisposable
#else
public struct TimedLock : IDisposable
#endif
{
public static TimedLock Lock (object o)
{
return Lock (o, TimeSpan.FromSeconds (10));
}
public static TimedLock Lock (object o, TimeSpan timeout)
{
TimedLock tl = new TimedLock (o);
if (!Monitor.TryEnter (o, timeout))
{
#if DEBUG
System.GC.SuppressFinalize(tl);
#endif
throw new LockTimeoutException ();
}
return tl;
}
private TimedLock (object o)
{
target = o;
} private object target;
public void Dispose ()
{
Monitor.Exit (target);
// 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
GC.SuppressFinalize(this);
#endif
}
#if DEBUG
~TimedLock()
{
// If this finalizer runs, someone somewhere failed to
// call Dispose, which means we've failed to leave
// a monitor!
System.Diagnostics.Debug.Fail("Undisposed lock");
}
#endif
}
public class LockTimeoutException : Exception
{
public LockTimeoutException () : base("Timeout waiting for lock")
{
}
}
In order to use it:
using (TimedLock.Lock(obj))
{
}
You can read the Ian's post here.