Introduction
I am hoping that this will be the first in a series of articles building "bricks" of reusable code. First up is ReaderWriterLock
s. In this article, I will show a simpler way of dealing with ReaderWriterLock
s.
Background
The .NET Framework provides several threading locking primitives. Most of these aren't really appropriate for something like a collection where we need fast read access, and some write access, this is especially true if we don't need to lock the item when doing reads. The ReaderWriterLock
gives us exactly this, the ability to only lock when doing writes. The code that you need to use is sometime kludgy and monotonous, especially if you are doing something like protecting a complex class with lots of functions. A typical example of a ReaderWriterLock
locked function in a class would look something like this:
public class Class1
{
private ReaderWriterLock _readerWriterLock = new ReaderWriterLock();
public void AnyNonWritingFunction()
{
try
{
_readerWriterLock.AcquireReaderLock(Timeout.Infinite);
}
finally
{
_readerWriterLock.ReleaseReaderLock();
}
}
public void WriteOnlyFunction()
{
try
{
_readerWriterLock.AcquireWriterLock(Timeout.Infinite);
}
finally
{
_readerWriterLock.ReleaseWriterLock();
}
}
public void FunctionThatMightWrite(bool doWrite)
{
try
{
_readerWriterLock.AcquireReaderLock(Timeout.Infinite);
if (doWrite)
{
LockCookie cookie;
try
{
cookie = _readerWriterLock.UpgradeToWriterLock(Timeout.Infinite);
}
finally
{
_readerWriterLock.DowngradeFromWriterLock(ref cookie);
}
}
}
finally
{
_readerWriterLock.ReleaseReaderLock();
}
}
}
Personally, I thought this could be a lot easier if we wrap the functions into IDisposable
objects, so I could use the using
statement. One last benefit to using classes is that messy LockCookie
returned by the UpgradeToWriterLock
. The class can hold that data for me, preventing additional clutter in my code.
So to start with, I decided to create a ReaderLock
and a WriterLock
class, and a base class (LockBase
) to inherit these two classes from, that way I don't have to rewrite the functions.
public abstract class LockBase : IDisposable
{
private ReaderWriterLock _lock = null;
private bool _disposed;
~LockBase()
{
Dispose(false);
}
protected LockBase(ReaderWriterLock @lock)
{
_lock = @lock;
}
public bool IsDisposed
{
get
{
return _disposed;
}
}
public void Dispose()
{
Dispose(true);
}
protected internal ReaderWriterLock Lock
{
get
{
if (_disposed)
{
throw new ObjectDisposedException
("Can't access lock for disposed object.");
}
return _lock;
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
if (disposing)
{
GC.SuppressFinalize(this);
}
if (_lock != null)
{
_lock = null;
}
}
}
protected void AcquireReaderLock()
{
AcquireReaderLock(Timeout.Infinite);
}
protected void AcquireReaderLock(int milliseconds)
{
Debug.WriteLine("Acquiring ReaderLock");
Lock.AcquireReaderLock(milliseconds);
}
protected void AcquireWriterLock()
{
AcquireWriterLock(Timeout.Infinite);
}
protected void AcquireWriterLock(int milliseconds)
{
Debug.WriteLine("Acquiring WriterLock");
Lock.AcquireWriterLock(milliseconds);
}
protected void ReleaseReaderLock()
{
Debug.WriteLine("Releasing ReaderLock");
if (Lock.IsReaderLockHeld)
{
Lock.ReleaseReaderLock();
}
}
}
The other two classes ended up being much smaller, since all the real work is being done in this base class. Note the virtual Dispose
method, this is all I will need to override
in the inherited classes.
The ReaderLock
will be the next most important class, and actually the most commonly used class.
public class ReaderLock : LockBase
{
public ReaderLock(ReaderWriterLock readerWriterLock)
: base(readerWriterLock)
{
AcquireReaderLock();
}
~ReaderLock()
{
Dispose(false);
}
protected override void Dispose(bool disposing)
{
if (IsDisposed)
{
return;
}
Debug.WriteLine("Disposing ReaderLock");
try
{
ReleaseReaderLock();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
base.Dispose(disposing);
}
}
}
This class simply acquires the read lock from the ReaderWriterLock
passed in, and releases it automatically on dispose.
public class WriterLock : LockBase
{
private LockCookie _cookie;
private bool _upgraded;
public WriterLock(ReaderWriterLock readerWriterLock)
: base(readerWriterLock)
{
AcquireWriterLock();
}
public WriterLock(ReaderLock readerLock)
: base(readerLock.Lock)
{
UpgradeToWriterLock();
}
private void UpgradeToWriterLock()
{
UpgradeToWriterLock(Timeout.Infinite);
}
private void UpgradeToWriterLock(int milliseconds)
{
Debug.WriteLine("Upgrading ReaderLock to WriterLock.");
_cookie = Lock.UpgradeToWriterLock(milliseconds);
_upgraded = true;
}
protected override void Dispose(bool disposing)
{
if (IsDisposed)
{
return;
}
Debug.WriteLine("Disposing WriteLock");
try
{
if (Lock.IsWriterLockHeld)
{
if (!_upgraded)
{
Lock.ReleaseWriterLock();
}
else
{
Lock.DowngradeFromWriterLock(ref _cookie);
_upgraded = false;
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
base.Dispose(disposing);
}
}
}
This class is probably the most complex as it acts as a pure write lock if there is not a read lock held by the ReaderWriterLock
passed in. Otherwise it upgrades the read lock to a write lock. Just like the ReaderLock
class, this WriterLock
releases the acquired lock automatically. However, if it upgraded a read lock, it will downgrade it instead of completely releasing it. This class also encapsulates and completely hides the LockCookie
and associated logic. This way code that uses these classes don't have to be littered with extra variables.
Using the Code
The simplest way to use these classes are in the using
block. As you can see, this drastically reduces the size of your code, and makes the locking code less distracting.
public class Class1
{
private ReaderWriterLock _readerWriterLock = new ReaderWriterLock();
public void AnyNonWritingFunction()
{
using (new ReaderLock(_readerWriterLock))
{
}
}
public void WriteOnlyFunction()
{
using (new WriterLock(_readerWriterLock))
{
}
}
public void FunctionThatMightWrite(bool doWrite)
{
using (ReaderLock readerLock = new ReaderLock(_readerWriterLock))
{
if (doWrite)
{
using (new WriterLock(readerLock))
{
}
}
}
}
}
Points of Interest
At this point, I will give a few explanations.
I used ReaderWriterLock
rather than ReaderWriterLockSlim
for several reasons:
ReaderWriterLockSlim
doesn't support recursive locking by default, and when I tested recursive locking, it appeared to have a huge memory leak.
ReaderWriterLockSlim
isn't supported on .NET Framework 2.0.
I didn't bother implementing constructors for Timeout types or even the milliseconds timeouts, to keep the code simple, real classes would likely need those features.
These classes work as shown in this code, but aren't designed to handle being created on one thread and disposed on another.
History
- 9th January, 2010: Initial post