Introduction
The following classes should provide an easy to use and read way of dealing with the ReaderWriteLockSlim
class.
Background
In scenarios needing ReaderWriterLock[Slim]
objects, the following pieces of code usually spread in the classes that should protect data:
public class Class1
{
private ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();
public void Read()
{
try
{
_readerWriterLock.EnterReadLock();
}
finally
{
_readerWriterLock.ExitReadLock();
}
}
public void Write()
{
try
{
_readerWriterLock.EnterWriteLock();
}
finally
{
_readerWriterLock.ExitWriteLock();
}
}
}
In most cases, at least 2 different methods should be called on the ReaderWriterLockSlim
to have a correct behaviour. This can be decreased by using an IDisposable
wrapper of the ReaderWriterLockSlim
.
Using the Code
The goal is having the code read like this:
public class Class1
{
private ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();
public void Read()
{
using (new ReaderGuard(_readerWriterLock))
{
}
}
public void Write()
{
using (new WriterGuard(_readerWriterLock))
{
}
}
}
The tedious 'try
-finally'
block is replaced by a tedious 1-liner 'using
'.
For the UpgradeableReadLock
, this:
public void ReadThenWrite()
{
try
{
_readerWriterLock.EnterUpgradeableReadLock();
if (condition)
{
try
{
_readerWriterLock.EnterWriteLock();
}
finally
{
_readerWriterLock.ExitWriteLock();
}
}
}
finally
{
_readerWriterLock.ExitUpgradeableReadLock();
}
}
can be replaced by this:
public void ReadThenWrite()
{
using (var upgradeableGuard = new UpgradeableGuard(_readerWriterLock))
{
if (condition)
{
using (upgradeableGuard.UpgradeToWriterLock())
{
}
}
}
}
The nested using
can be omitted in the current implementation as the UpgradeableGuard
can take care of calling ExitWriteLock()
:
public void ReadThenWrite()
{
using (var upgradeableGuard = new UpgradeableGuard(_readerWriterLock))
{
if (condition)
{
upgradeableGuard.UpgradeToWriterLock())
}
}
}
Implementation
ReaderGuard
Nothing especially tricky in this one:
public class ReaderGuard : IDisposable
{
private readonly ReaderWriterLockSlim _readerWriterLock;
public ReaderGuard(ReaderWriterLockSlim readerWriterLock)
{
_readerWriterLock = readerWriterLock;
_readerWriterLock.EnterReadLock();
}
public void Dispose()
{
_readerWriterLock.ExitReadLock();
}
}
WriterGuard
Almost the same implementation, but a little tweak to reuse the class in the UpgradeableGuard
:
public class WriterGuard : IDisposable
{
private ReaderWriterLockSlim _readerWriterLock;
private bool IsDisposed { get { return _readerWriterLock == null; } }
public WriterGuard(ReaderWriterLockSlim readerWriterLock)
{
_readerWriterLock = readerWriterLock;
_readerWriterLock.EnterWriteLock();
}
public void Dispose()
{
if (IsDisposed)
throw new ObjectDisposedException(this.ToString());
_readerWriterLock.ExitWriteLock();
_readerWriterLock = null;
}
}
UpgradeableGuard
This one involves two classes, the UpgradeableGuard
itself and a nested UpgradedGuard
class used to implement the upgraded state of the lock:
public class UpgradeableGuard : IDisposable
{
private readonly ReaderWriterLockSlim _readerWriterLock;
private UpgradedGuard _upgradedLock;
public UpgradeableGuard(ReaderWriterLockSlim readerWriterLock)
{
_readerWriterLock = readerWriterLock;
_readerWriterLock.EnterUpgradeableReadLock();
}
public IDisposable UpgradeToWriterLock()
{
if (_upgradedLock == null)
{
_upgradedLock = new UpgradedGuard(this);
}
return _upgradedLock;
}
public void Dispose()
{
if (_upgradedLock != null)
{
_upgradedLock.Dispose();
}
_readerWriterLock.ExitUpgradeableReadLock();
}
}
When the guard upgrades to a writer lock, a new IDisposable
wrapper is created, for the first time only. The UpgradeableGuard
stores a reference to the UpgradedGuard
in case of forgetting to put the UpgradeToWriterLock()
call in a 'using
' statement.
public class UpgradeableGuard : IDisposable
{
private class UpgradedGuard : IDisposable
{
private UpgradeableGuard _parentGuard;
private WriterGuard _writerLock;
public UpgradedGuard(UpgradeableGuard parentGuard)
{
_parentGuard = parentGuard;
_writerLock = new WriterGuard(_parentGuard._readerWriterLock);
}
public void Dispose()
{
_writerLock.Dispose();
_parentGuard._upgradedLock = null;
}
}
}
Points of Interest
These three classes avoid repeating the 'try
-Enter
-finally
-Exit
' through the code and reduces the code required to handle ReaderWriterLockSlims
, thus helping to keep focus on the part of the code that does the actual work.
.NET Software Engineer, interested in clean coding, architecture, code quality improvement.