Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

ReaderWriterSlimLock: Conquering Mismatched Enter and Exit Calls

, 26 Nov 2012 LGPL3
Rate this:
Please Sign up or sign in to vote.
The ReaderWriterLockSlim class is used to protect a resource that is read by multiple threads and written to by one thread at a time.

The ReaderWriterLockSlim class is used to protect a resource that is read by multiple threads and written to by one thread at a time. The class exists in the desktop FCL, but is noticeably absent from the Silverlight FCL. To readily support both platforms, some time ago, I incorporated the open-source mono implementation of the ReaderWriterLockSlim class into the Silverlight version of my core library, which is downloadable from http://calcium.codeplex.com/SourceControl/list/changesets

ReaderWriterLockSlim is a light-weight alternative to the Monitor class, for providing for concurrency. I say light-weight because the Monitor class only provides for exclusive access; where only a single thread can enter, regardless of the kind of operation.

The Monitor class, however, has an advantage: its syntax can be simplified using the lock statement, as shown in the following code snippet:

System.Object resourceLock = new System.Object();
System.Threading.Monitor.Enter(resourceLock);
try
{
    DoSomething();
}
finally
{
    System.Threading.Monitor.Exit(resourceLock);
}

Which is equivalent to the following, when using a lock statement:

lock (x)
{
    DoSomething();
}

Using the lock statement means that we never get caught out forgetting to exit the lock, which could lead to a thread being blocked indefinitely.

No such baked-in infrastructure exists for the ReaderWriterSlimLock. So, I've created a number of extension methods to simulate the lock statement for the ReaderWriterLockSlim.

Using Extension Methods to Simulate Lock Syntax

If you've used the ReaderWriterSlimLock a lot, you'll know that mismatching Enter and Exit calls can be easy to do, especially when pasting code. For example, in the following excerpt we see how a call to enter a write protected section of code is mismatched with a call to exit a read section:

ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim();

try
{
    lockSlim.EnterWriteLock();
    DoSomething();
}
finally
{
    lockSlim.ExitReadLock();
}

This code will result in a System.Threading. SynchronizationLockException being raised.

The extension methods that have been written, allow a Func or an Action to be supplied, which will be performed within a try/finally block. The previous excerpt can be rewritten more concisely as:

ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim();
lockSlim.PerformUsingWriteLock(() => DoSomething);

In the downloadable code I have included a unit test to demonstrate how the ReaderWriterLockSlim extension methods are used, (see Listing 1).

Listing 1: LockSlimTests Class

[TestClass]
public class LockSlimTests : SilverlightTest
{
    readonly static List<string> sharedList = new List<string>();
 
    [TestMethod]
    public void ExtensionsShouldPerformActions()
    {
        ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim();
 
        string item1 = "test";
 
        //    Rather than this code:
        //            try
        //            {
        //                lockSlim.EnterWriteLock();
        //                sharedList.Add(item1);
        //            }
        //            finally
        //            {
        //                lockSlim.ExitWriteLock();
        //            }
        //    We can write this one liner:
        lockSlim.PerformUsingWriteLock(() => sharedList.Add(item1));
 
        //    Rather than this code:
        //            string result;
        //            try
        //            {
        //                lockSlim.EnterReadLock();
        //                result = sharedList[0];
        //            }
        //            finally
        //            {
        //                lockSlim.ExitReadLock();
        //            }
        //    We can write this one liner:
        string result = lockSlim.PerformUsingReadLock(() => sharedList[0]);
 
        Assert.AreEqual(item1, result);
 
        string item2 = "test2";
 
        //    Rather than this code:
        //            try
        //            {
        //                lockSlim.EnterUpgradeableReadLock();
        //                if (!sharedList.Contains(item2))
        //                {
        //                    try
        //                    {
        //                        lockSlim.EnterWriteLock();
        //                        sharedList.Add(item2);
        //                    }
        //                    finally
        //                    {
        //                        lockSlim.ExitWriteLock();
        //                    }
        //                }
        //            }
        //            finally
        //            {
        //                lockSlim.ExitUpgradeableReadLock();
        //            }
        //    We can write this:
        lockSlim.PerformUsingUpgradeableReadLock(() => 
            {
                if (!sharedList.Contains(item2))
                {
                    lockSlim.PerformUsingWriteLock(() => sharedList.Add(item2));
                }
            });
 
        //    Rather than this code:
        //            try
        //            {
        //                lockSlim.EnterReadLock();
        //                result = sharedList[1];
        //            }
        //            finally
        //            {
        //                lockSlim.ExitReadLock();
        //            }
        //    We can write this:
        result = lockSlim.PerformUsingReadLock(() => sharedList[1]);
 
        Assert.AreEqual(item2, result);
    }
 
}

The result of executing this test is shown in Figure 1.

Figure 1: Result of running the ReaderWriterLockSlimExtension tests.

The ReaderWriterLockSlimExtensions accept a simple Action or a Func to return value, and take care of calling the appropriate Enter and Exit methods, inside a try/finally block, (see Listing 2).

Listing 2: ReaderWriterLockSlimExtensions Class

public static class ReaderWriterLockSlimExtensions
{
    public static void PerformUsingReadLock(this ReaderWriterLockSlim readerWriterLockSlim, Action action)
    {
        ArgumentValidator.AssertNotNull(readerWriterLockSlim, "readerWriterLockSlim");
        ArgumentValidator.AssertNotNull(action, "action");
        try
        {
            readerWriterLockSlim.EnterReadLock();
            action();
        }
        finally
        {
            readerWriterLockSlim.ExitReadLock();
        }
    }
 
    public static T PerformUsingReadLock<T>(this ReaderWriterLockSlim readerWriterLockSlim, Func<T> action)
    {
        ArgumentValidator.AssertNotNull(readerWriterLockSlim, "readerWriterLockSlim");
        ArgumentValidator.AssertNotNull(action, "action");
        try
        {
            readerWriterLockSlim.EnterReadLock();
            return action();
        }
        finally
        {
            readerWriterLockSlim.ExitReadLock();
        }
    }
 
    public static void PerformUsingWriteLock(this ReaderWriterLockSlim readerWriterLockSlim, Action action)
    {
        ArgumentValidator.AssertNotNull(readerWriterLockSlim, "readerWriterLockSlim");
        ArgumentValidator.AssertNotNull(action, "action");
        try
        {
            readerWriterLockSlim.EnterWriteLock();
            action();
        }
        finally
        {
            readerWriterLockSlim.ExitWriteLock();
        }
    }
 
    public static T PerformUsingWriteLock<T>(this ReaderWriterLockSlim readerWriterLockSlim, Func<T> action)
    {
        ArgumentValidator.AssertNotNull(readerWriterLockSlim, "readerWriterLockSlim");
        ArgumentValidator.AssertNotNull(action, "action");
        try
        {
            readerWriterLockSlim.EnterWriteLock();
            return action();
        }
        finally
        {
            readerWriterLockSlim.ExitWriteLock();
        }
    }
 
    public static void PerformUsingUpgradeableReadLock(this ReaderWriterLockSlim readerWriterLockSlim, Action action)
    {
        ArgumentValidator.AssertNotNull(readerWriterLockSlim, "readerWriterLockSlim");
        ArgumentValidator.AssertNotNull(action, "action");
        try
        {
            readerWriterLockSlim.EnterUpgradeableReadLock();
            action();
        }
        finally
        {
            readerWriterLockSlim.ExitUpgradeableReadLock();
        }
    }
 
    public static T PerformUsingUpgradeableReadLock<T>(this ReaderWriterLockSlim readerWriterLockSlim, Func<T> action)
    {
        ArgumentValidator.AssertNotNull(readerWriterLockSlim, "readerWriterLockSlim");
        ArgumentValidator.AssertNotNull(action, "action");
        try
        {
            readerWriterLockSlim.EnterUpgradeableReadLock();
            return action();
        }
        finally
        {
            readerWriterLockSlim.ExitUpgradeableReadLock();
        }
    }
}

The ArgumentValidator class is present in my base library. Calls to its AssertNotNull can be replaced with a null check, and if null throw an ArgumentNullException. If you'd like the code for the ArgumentValidator etc., you can find it in the Core class library in the source available at http://calcium.codeplex.com/SourceControl/list/changesets

Conclusion

In this post we have seen how extension methods can be used to ensure that the ReaderWriterLockSlim class is correctly exited after a thread critical region. This avoids the problem of mismatched Enter and Exit calls, which can result in exceptions and indefinite blocking.

I hope you have enjoyed this post, and that you find the code and ideas presented within it useful.

Download code: ReaderWriterSlimLockExtensions.zip (1.14 mb)

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

Daniel Vaughan
President Outcoder
Switzerland Switzerland
Daniel Vaughan is a Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologies--in particular WPF, WinRT, Windows Phone, and also Xamarin.Forms.
 
Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.
 
Daniel is the developer behind several acclaimed Windows Phone apps including Surfy, Intellicam, and Splashbox; and is the creator of a number of popular open-source projects including Calcium SDK, and Clog.
 
Would you like Daniel to bring value to your organisation? Please contact

Daniel's Blog | MVP profile | Follow on Twitter
 
Windows Phone Experts
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
GeneralA small problem... PinmvpPaulo Zemek1-Dec-12 15:14 
GeneralRe: A small problem... PinmemberDaniel Vaughan3-Dec-12 1:58 
QuestionProblem with code snippet formatting... PinmemberGilly Barr26-Nov-12 0:31 
AnswerRe: Problem with code snippet formatting... PinmemberDaniel Vaughan26-Nov-12 1:19 
QuestionSome issues that may have been overlooked PinmemberTravis Hall10-Jul-11 22:05 
AnswerRe: Some issues that may have been overlooked PinmvpDaniel Vaughan10-Jul-11 22:26 
GeneralRe: Some issues that may have been overlooked PinmemberTravis Hall11-Jul-11 0:52 
GeneralRe: Some issues that may have been overlooked PinmvpDaniel Vaughan11-Jul-11 2:39 
Hi Travis,
 
Thanks a lot for your thoughtful remarks.
 
I favoured placing the enter call in the try to avoid deadlocks at all costs. The downside is, as you say, not knowing if we should exit. Also, if the lock is not held then exiting will raise an exception. I will try and address issues like this in my next update.
 
Regarding the nesting of locks: yes, that's the way it works. There is, however, one exception: downgrading an upgradable read lock to a read-only lock; which can be done like so:
 
lockSlim.EnterUpgradeableReadLock();
try
{
    lockSlim.EnterReadLock();
    try
    {
        lockSlim.ExitUpgradeableReadLock();
        // In read mode.
    }
    finally
    {
        lockSlim.ExitReadLock();
    }
}
catch (Exception)
{
    if (lockSlim.IsUpgradeableReadLockHeld)
    {
        lockSlim.ExitUpgradeableReadLock();
    }
}
 
Cheers,
Daniel
Daniel Vaughan
Twitter | Blog | Microsoft MVP | Projects: Calcium SDK, Clog | LinkedIn

GeneralRe: Some issues that may have been overlooked [modified] PinmemberTravis Hall13-Jul-11 17:43 
GeneralRe: Some issues that may have been overlooked PinmvpDaniel Vaughan13-Jul-11 23:36 

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
Web03 | 2.8.141216.1 | Last Updated 26 Nov 2012
Article Copyright 2010 by Daniel Vaughan
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid