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

Tagged as

ReaderWriterLock Wrapper Classes

, 9 Jan 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
Simplifying locking with disposable objects

Introduction

I am hoping that this will be the first in a series of articles building "bricks" of reusable code. First up is ReaderWriterLocks. In this article, I will show a simpler way of dealing with ReaderWriterLocks.

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); 
            // do all (read) work here
        }
        finally
        {
            _readerWriterLock.ReleaseReaderLock();
        }
    }
    public void WriteOnlyFunction()
    {
        try
        {
            _readerWriterLock.AcquireWriterLock(Timeout.Infinite);
            //do all (write) work
        }
        finally
        {
            _readerWriterLock.ReleaseWriterLock();
        }
    }
    public void FunctionThatMightWrite(bool doWrite)
    {
        try
        {
            _readerWriterLock.AcquireReaderLock(Timeout.Infinite);
            if (doWrite)
            {
                LockCookie cookie;
                try
                {
                    cookie = _readerWriterLock.UpgradeToWriterLock(Timeout.Infinite);
                    //do all write work 
                }
                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);
            //eat error quietly
        }
        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);
            //eat error quietly
        }
        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))
        {
            // do all (read) work here
        }
    }
    public void WriteOnlyFunction()
    {
        using (new WriterLock(_readerWriterLock))
        {
            //do all (write) work
        }
    }
    public void FunctionThatMightWrite(bool doWrite)
    {
        using (ReaderLock readerLock = new ReaderLock(_readerWriterLock))
        {
            if (doWrite)
            {
                using (new WriterLock(readerLock))
                {
                    //do all write work 
                }
            }
        }
    }
}

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

License

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

Share

About the Author

Alaric Dailey
Software Developer (Senior) Pengdows
United States United States
Currently looking for new contracts in Omaha NE or telecommute opportunities.

Comments and Discussions

 
SuggestionOver-engineered... Pinmemberessence14-Feb-13 8:24 
GeneralGood article PinmvpJosh Fischer5-Feb-10 10:18 
QuestionWhy no extension method? PinmemberSimon Allaeys10-Jan-10 11:57 
AnswerRe: Why no extension method? PinmemberAlaric Dailey10-Jan-10 14:28 
GeneralReaderWriterLock vs Slim Pinmemberjpmik10-Jan-10 2:28 
GeneralRe: ReaderWriterLock vs Slim PinmemberAlaric Dailey10-Jan-10 4:16 
GeneralRe: ReaderWriterLock vs Slim Pinmemberjpmik10-Jan-10 5:49 
GeneralNice Pinmemberkosat10-Jan-10 2:07 
GeneralRe: Nice PinmemberAlaric Dailey10-Jan-10 4:26 
GeneralGreat idea PinmemberDaniel Vaughan10-Jan-10 1:32 
GeneralRe: Great idea PinmemberAlaric Dailey10-Jan-10 3:59 
GeneralNice PinmentorNick Butler9-Jan-10 22:50 

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 | Terms of Use | Mobile
Web02 | 2.8.141220.1 | Last Updated 9 Jan 2010
Article Copyright 2010 by Alaric Dailey
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid