This is part 3/5 of my Deterministic Finalization and IDisposable post series.
For the first example of a useful custom class which implements IDisposable, I will simply link to and reproduce Ian Griffith’s TimedLock — an enhancement of the C# lock statement which allows the specification of a timeout period instead of blocking forever while trying to obtain the lock.
usingSystem;
usingSystem.Threading;
// Thanks to Eric Gunnerson for recommending this be a struct rather// than a class -- avoids a heap allocation.// Thanks to Change Gillespie and Jocelyn Coulmance for pointing out// the bugs that then crept in when I changed it to use struct...// Thanks to John Sands for providing the necessary incentive to make// me invent a way of using a struct in both release and debug builds// without losing the debug leak tracking.publicstructTimedLock : IDisposable
{
publicstatic TimedLock Lock (object o)
{
return Lock (o, TimeSpan.FromSeconds (10));
}
publicstatic TimedLock Lock (object o, TimeSpan timeout)
{
TimedLock tl = new TimedLock (o);
if (!Monitor.TryEnter (o, timeout))
{
#if DEBUG
System.GC.SuppressFinalize(tl.leakDetector);
#endifthrownew LockTimeoutException ();
}
return tl;
}
private TimedLock (object o)
{
target = o;
#if DEBUG
leakDetector = new Sentinel();
#endif }
privateobject target;
publicvoid 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(leakDetector);
#endif }
#if DEBUG
// (In Debug mode, we make it a class so that we can add a finalizer// in order to detect when the object is not freed.)privateclassSentinel {
~Sentinel()
{
// 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");
}
}
private Sentinel leakDetector;
#endif}
publicclassLockTimeoutException : ApplicationException
{
public LockTimeoutException () : base("Timeout waiting for lock")
{
}
}
It is trivial to use TimedLock instead of lock in your applications. Simply change statements from:
This is part 1/5 of my Deterministic Finalization and IDisposable post series.
This topic has been covered many times by many others (such as here and here), so if you are familiar with C#’s using statement and IDisposable interface, feel free to skip this post. I’m writing this introduction to provide the necessary background information to set up a series of subsequent posts.
Garbage collection, found in languages such as C# and Java (among many others), is a very useful feature: it largely alleviates the need for a programmer to manually handle resource management. The most commonly cited benefit is that garbage collection eliminates the need for the programmer to explicitly call heap memory management functions such as malloc and free; instead, the garbage collector automatically keeps track of whether objects are still in use and frees them when they are no longer needed.1 However, in addition to handling memory management, garbage collection may also release other scarce resources upon cleanup, such as file locks or network connections.