Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ReaderWriterLock Wrapper Classes

0.00/5 (No votes)
9 Jan 2010 1  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here