Click here to Skip to main content
15,890,438 members
Articles / Programming Languages / C#
Tip/Trick

ReaderWriterLockSlim and the IDisposable Wrapper

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
2 Oct 2013CPOL1 min read 22.4K   5   6
Clean code and DRY to handle ReaderWriterLockSlim

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:

C#
public class Class1
{
    private ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();
    public void Read()
    {
        try
        {
            _readerWriterLock.EnterReadLock();
            // read stuff...
        }
        finally
        {
            _readerWriterLock.ExitReadLock();
        }
    }
    public void Write()
    {
        try
        {
            _readerWriterLock.EnterWriteLock();
            // write stuff...
        }
        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:

C#
public class Class1
{
    private ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();
    public void Read()
    {
        using (new ReaderGuard(_readerWriterLock))
        {
            // read stuff...
        }
    }
    public void Write()
    {
        using (new WriterGuard(_readerWriterLock))
        {
            // write stuff...
        }
    }
}  

The tedious 'try-finally' block is replaced by a tedious 1-liner 'using'.

For the UpgradeableReadLock, this:

C#
public void ReadThenWrite()
{
    try
    {
        _readerWriterLock.EnterUpgradeableReadLock();
        // get data for condition
        if (condition)
        {
            try
            {
                _readerWriterLock.EnterWriteLock();
                // write stuff
            }
            finally
            {
                _readerWriterLock.ExitWriteLock();
            }
                
        }
    }
    finally
    {
        _readerWriterLock.ExitUpgradeableReadLock();
    }
}  

can be replaced by this:

C#
public void ReadThenWrite()
{
    using (var upgradeableGuard = new UpgradeableGuard(_readerWriterLock))
    {
        // get data for condition
        if (condition)
        {
            using (upgradeableGuard.UpgradeToWriterLock())
            {
                // write stuff
            }
        }
    }
} 

The nested using can be omitted in the current implementation as the UpgradeableGuard can take care of calling ExitWriteLock():

C#
public void ReadThenWrite()
{
    using (var upgradeableGuard = new UpgradeableGuard(_readerWriterLock))
    {
        // get data for condition
        if (condition)
        {
            upgradeableGuard.UpgradeToWriterLock())
            // write stuff
        }
    }
}   

Implementation

ReaderGuard

Nothing especially tricky in this one:

C#
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:

C#
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:

C#
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.

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
France France
.NET Software Engineer, interested in clean coding, architecture, code quality improvement.

Comments and Discussions

 
SuggestionMemory Pin
xmedeko13-Jan-16 0:54
xmedeko13-Jan-16 0:54 
QuestionIt is not possible to get any source related to article Pin
petr1234512-Jul-14 23:30
petr1234512-Jul-14 23:30 
AnswerRe: It is not possible to get any source related to article Pin
Sebastien GASPAR23-Jul-14 22:14
Sebastien GASPAR23-Jul-14 22:14 
QuestionSeens very familiar Pin
eddie_garmon3-Oct-13 4:47
eddie_garmon3-Oct-13 4:47 
AnswerRe: Seens very familiar Pin
Sebastien GASPAR3-Oct-13 5:07
Sebastien GASPAR3-Oct-13 5:07 
GeneralRe: Seens very familiar Pin
Richard Deeming10-Oct-13 9:08
mveRichard Deeming10-Oct-13 9:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.