ReaderWriterLock(Slim) Extension Methods

With the .NET framework 3.5 came a new implementation of the reader writer lock that solved many flaws of the original design. Joe Duffy describes in detail what's new in ReaderWriterLockSlim, how it differs from the old version and why a new class was needed to do the same thing.

In most of my applications, when I use a reader writer lock I use them a lot.  Fortunately, using the lock is straight forward:



var rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
rwLock.EnterReadLock();

try {
    // do stuff  
} catch (Exception ex) {
    // handle exception 
} finally {
    // release the lock     
    rwLock.ExitReadLock(); 
}


 

But when you're acquiring/releasing locks a lot, your code is littered with try-catch-finally's.

This isn't an earth-shattering-world-heating-climate-cooling-mass-destruction problem, but I'm a big fan of simplifying and cleaning up code where ever I can - for no other reason than the KISS principle.

In C# 3.0, extension methods and lambda's provide us with a great way to extend and wrap existing class behaviour. I used lambdas in continuations in my ActionDisposable post to give a collection class some public locking.

With extension methods, I can extend the ReaderWriterLockSlim class itself to simplify the use of the lock.



using System;

namespace System.Threading {

	public static class ReaderWriteLockExtensions {

		struct Disposable : IDisposable {
			readonly Action action;

			public Disposable(Action action) {
				this.action = action;
			}

			public void Dispose() {
				action();
			}

		}


		#region Lock Helpers

		public static IDisposable ReadLock(this ReaderWriterLockSlim l) {
			l.EnterReadLock();
			return new Disposable(l.ExitReadLock);
		}

		public static IDisposable UpgradableReadLock(this ReaderWriterLockSlim l) {
			l.EnterUpgradeableReadLock();
			return new Disposable(l.ExitUpgradeableReadLock);
		}

		public static IDisposable WriteLock(this ReaderWriterLockSlim l) {
			l.EnterWriteLock();
			return new Disposable(l.ExitWriteLock);
		}

		#endregion
	}
}



 

ReadLock(), UpgradableReadLock() and WriteLock() return IDisposable, which can be used in using() blocks that automatically clean-up even when exceptions are thrown.




var rwLock = new ReaderWriterLockSlim();
	using (rwLock.ReadLock()) {
	// do reading stuff

}



It's a little easier to read, and has certainly cleaned up our heavily locked code.

Tags: ,

posted on Friday, August 08, 2008 4:28 PM Print
Comments
Gravatar
# re: ReaderWriterLock(Slim) Extension Methods
Wayne
8/28/2008 3:08 AM
  
You should change your Disposable class to a struct and then implement a Sentinel class to check for leaks as shown here:

http://www.interact-sw.co.uk/iangblog/2004/04/26/yetmoretimedlocking

(Oh No! Not the TimedLock Again!)
Gravatar
# re: ReaderWriterLock(Slim) Extension Methods
Vijay Santhanam
9/7/2008 9:49 PM
  
Thanks Wayne, not sure I need the Sentinel there, but I changed the ActionDisposable to a struct.
Gravatar
# re: ReaderWriterLock(Slim) Extension Methods
Nathan Phillips
11/18/2008 12:03 PM
  
If an exception (typically a thread abort) is thrown after EnterReadLock but before the start of the using block then the lock will be left locked forever. The following code avoids this problem by providing a method on your disposable object to call within the using block to obtain the lock:
using System;
using System.Threading;

namespace NobleTech.Products.Library
{
class ReaderWriterLockMgr : IDisposable
{
private ReaderWriterLockSlim _readerWriterLock = null;
private bool _isDisposed = false;
private enum LockTypes { None, Read, Write }
LockTypes _enteredLockType = LockTypes.None;

public ReaderWriterLockMgr(ReaderWriterLockSlim ReaderWriterLock)
{
_readerWriterLock = ReaderWriterLock;
}

public void EnterReadLock()
{
_readerWriterLock.EnterReadLock();
_enteredLockType = LockTypes.Read;
}

public void EnterWriteLock()
{
_readerWriterLock.EnterWriteLock();
_enteredLockType = LockTypes.Write;
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
switch (_enteredLockType)
{
case LockTypes.Read:
_readerWriterLock.ExitReadLock();
break;
case LockTypes.Write:
_readerWriterLock.ExitWriteLock();
break;
}
}
}

_isDisposed = true;
}
}
}
Gravatar
# Potential Deadlock
Nathan Phillips
11/24/2008 11:31 PM
  
Vijay wanted some more details on what exactly causes the deadlock situation with the original code.
I have posted a detailed article addressing this question here: http://www.nobletech.co.uk/Articles/
PS. My code posted above doesn't check what you're not already in a lock before acquiring a new one. Of course the lock itself will throw an exception if you haven't allowed recursion on it, but if you have then you will need to do this to make sure that each lock you acquire is released correctly at the right time. The full code for the lock manager including these checks is available on the page I mentioned.
Gravatar
# Link corrected
Nathan Phillips
11/24/2008 11:33 PM
  
Sorry, site addresses aren't automatically turned into links, hope this helps: http://www.nobletech.co.uk/Articles/
Title *
Name *
Email
Website
Comments (no HTML) *  
Please add 7 and 3 and type the answer here:
Posts
8
Comments
28
Trackbacks
0