Click here to Skip to main content
Click here to Skip to main content

ReaderWriterLockSlim and the IDisposable Wrapper

, 2 Oct 2013
Rate this:
Please Sign up or sign in to vote.
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:

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:

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:

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:

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():

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:

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.

License

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

Share

About the Author

Sebastien GASPAR
Software Developer (Senior)
France France
.NET Software Engineer, interested in clean coding, architecture, code quality improvement.
Follow on   LinkedIn

Comments and Discussions

 
QuestionIt is not possible to get any source related to article Pinmemberpetr1234512-Jul-14 23:30 
AnswerRe: It is not possible to get any source related to article PinmemberSebastien GASPAR23-Jul-14 22:14 
QuestionSeens very familiar Pinmembereddie_garmon3-Oct-13 4:47 
AnswerRe: Seens very familiar PinmemberSebastien GASPAR3-Oct-13 5:07 
GeneralRe: Seens very familiar PinprofessionalRichard Deeming10-Oct-13 9:08 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140821.2 | Last Updated 3 Oct 2013
Article Copyright 2013 by Sebastien GASPAR
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid