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

Tagged as

ReaderWriterSlimLock: Conquering Mismatched Enter and Exit Calls

, 26 Nov 2012
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 cofounder 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, and Windows Phone.
 
Daniel is the author of Windows Phone 7.5 Unleashed and Windows Phone 8 Unleashed, both published by SAMS.
 
Daniel is also the creator of a number of 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 14:14 
GeneralRe: A small problem... PinmemberDaniel Vaughan3-Dec-12 0:58 
QuestionProblem with code snippet formatting... PinmemberGilly Barr25-Nov-12 23:31 
AnswerRe: Problem with code snippet formatting... PinmemberDaniel Vaughan26-Nov-12 0:19 
QuestionSome issues that may have been overlooked PinmemberTravis Hall10-Jul-11 21:05 
AnswerRe: Some issues that may have been overlooked PinmvpDaniel Vaughan10-Jul-11 21:26 
GeneralRe: Some issues that may have been overlooked PinmemberTravis Hall10-Jul-11 23:52 
Hi Daniel.
 
You're right about the inside/outside issue not being unique to the technique you described, and the basic technique has proved useful to me this afternoon. (I had been using the technique you discussed with somebody else, involving an IDisposable wrapper around a ReaderWriterLockSlim, and was getting odd behaviour as a result of how it interacted with multithreaded code. Your technique proved to avoid the multithreading issues. Though, I've now solved the problem both ways, as using your technique put my mind on the right track for solving it the other way too.)
 
But the "traditional" examples I've seen, including from MSDN, do keep the enter call outside the try.
 
If an exception - ThreadAbortException or otherwise, with the exception of LockRecursionException - occurred during an enter call, we'd have no way of knowing whether an exit call is appropriate. Is there a reason to favour calling exit when it could just as well be the wrong move? Unless there is a guarantee of atomicity on the enter call, in which case it should certainly go outside, as any exception thrown during the call would then be guaranteed to leave the state unaltered.
 
As for the second issue, I think you're telling me that the appropriate usage is as per my parenthesised paragraph. In which case, yeah, I just need to make sure I read up on the appropriate usage before using upgradeable read locks (which I haven't had a good reason to use yet).
 
Anyway, thanks for the discussion, and the article.
 
Travis
GeneralRe: Some issues that may have been overlooked PinmvpDaniel Vaughan11-Jul-11 1:39 
GeneralRe: Some issues that may have been overlooked [modified] PinmemberTravis Hall13-Jul-11 16:43 
GeneralRe: Some issues that may have been overlooked PinmvpDaniel Vaughan13-Jul-11 22: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 | Mobile
Web02 | 2.8.140827.1 | Last Updated 26 Nov 2012
Article Copyright 2010 by Daniel Vaughan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid