Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / C#

ReaderWriterSlimLock: Conquering Mismatched Enter and Exit Calls

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
26 Nov 2012LGPL32 min read 33.8K   8   16
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 here.

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:

C#
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:

C#
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:

C#
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:

C#
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

C#
[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

C#
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 here.

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.

License

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


Written By
Engineer
Switzerland Switzerland
Daniel is a former senior engineer in Technology and Research at the Office of the CTO at Microsoft, working on next generation systems.

Previously Daniel was a nine-time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company.

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 mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Codon.

Would you like Daniel to bring value to your organisation? Please contact

Blog | Twitter


Xamarin Experts
Windows 10 Experts

Comments and Discussions

 
GeneralA small problem... Pin
Paulo Zemek1-Dec-12 14:14
mvaPaulo Zemek1-Dec-12 14:14 
GeneralRe: A small problem... Pin
Daniel Vaughan3-Dec-12 0:58
Daniel Vaughan3-Dec-12 0:58 
QuestionProblem with code snippet formatting... Pin
Gilly Barr25-Nov-12 23:31
Gilly Barr25-Nov-12 23:31 
AnswerRe: Problem with code snippet formatting... Pin
Daniel Vaughan26-Nov-12 0:19
Daniel Vaughan26-Nov-12 0:19 
QuestionSome issues that may have been overlooked Pin
Travis Hall10-Jul-11 21:05
Travis Hall10-Jul-11 21:05 
AnswerRe: Some issues that may have been overlooked Pin
Daniel Vaughan10-Jul-11 21:26
Daniel Vaughan10-Jul-11 21:26 
GeneralRe: Some issues that may have been overlooked Pin
Travis Hall10-Jul-11 23:52
Travis Hall10-Jul-11 23:52 
GeneralRe: Some issues that may have been overlooked Pin
Daniel Vaughan11-Jul-11 1:39
Daniel Vaughan11-Jul-11 1: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] Pin
Travis Hall13-Jul-11 16:43
Travis Hall13-Jul-11 16:43 
GeneralRe: Some issues that may have been overlooked Pin
Daniel Vaughan13-Jul-11 22:36
Daniel Vaughan13-Jul-11 22:36 
GeneralExcellent Idea Pin
merlin98115-Sep-10 1:39
professionalmerlin98115-Sep-10 1:39 
GeneralRe: Excellent Idea Pin
Daniel Vaughan15-Sep-10 6:16
Daniel Vaughan15-Sep-10 6:16 
QuestionHow about iDisposable "SlimLockReader" and "SlimLockWriter" classes? Pin
supercat915-Aug-10 9:41
supercat915-Aug-10 9:41 
AnswerRe: How about iDisposable "SlimLockReader" and "SlimLockWriter" classes? Pin
Daniel Vaughan15-Aug-10 12:48
Daniel Vaughan15-Aug-10 12:48 
GeneralRe: How about iDisposable "SlimLockReader" and "SlimLockWriter" classes? Pin
supercat916-Aug-10 5:54
supercat916-Aug-10 5:54 
GeneralRe: How about iDisposable "SlimLockReader" and "SlimLockWriter" classes? Pin
Daniel Vaughan16-Aug-10 10:32
Daniel Vaughan16-Aug-10 10:32 

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

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