Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A basic named lock class

3.29/5 (4 votes)
30 Jan 2008BSD2 min read 2   168  
This is a utility class for acquiring named locks.

Introduction

There are a few problems I've come across where synchronizing a particular "name" might be useful. One of the apps I work on makes heavy use of Lucene.NET; each page shows the results of a couple of queries. It doesn't make a whole ton of sense to run multiple, identical queries against Lucene at the same time, so I generate a key for each query, lock against that key, and let the first thread do the actual work while the others sit around sipping coffee.

Using the code

It's pretty simple to use this class. Create a new instance of NamedLock<string>, then call the Lock function within a using statement.

C#
NamedLock<string> locker = new NamedLock<string>();
var url = "http://services.digg.com...";
using (locker.Lock(url))
{
    //Do something synchronized
    var xml = new XmlDocument();
    xml.Load(url);
}

Points of interest

Have a look at the class itself. It's relatively small, and consists of three things:

  • The primary class with the Lock and Unlock functions
  • An internal Token class that implements IDisposable
  • An internal ReferenceCount class

NamedLock<t /> is pretty simple. It contains a Dictionary<T, ReferenceCount> to keep track of which names are currently locked, and provides utility functions for acquiring and releasing locks. Lock looks like this:

C#
public IDisposable Lock(T name, int timeout)
{
    Monitor.Enter(lockCollection);
    ReferenceCount obj = null;
    lockCollection.TryGetValue(name, out obj);
    if (obj == null)
    {
        obj = new ReferenceCount();
        Monitor.Enter(obj);
        lockCollection.Add(name, obj);
        Monitor.Exit(lockCollection);
    }
    else
    {
        obj.AddRef();
        Monitor.Exit(lockCollection);
        if (!Monitor.TryEnter(obj, timeout))
        {
            throw new TimeoutException(
                string.Format(
                "Timeout while waiting for lock on {0}", 
                name)
                );
        }
    }

    return new Token<T>(this, name);
}

This function locks the lockCollection, checks for an existing lock with the same name, adds one if it's the first, then locks and returns a token. There's a good reason it uses Monitor.Enter instead of a simple lock statement: you'll notice that if there's no current lock in the collection, we actually lock the sync object (named obj) before releasing the lock on the collection. If the lock does exist in the collection, we increment a reference counter, release the collection lock, and then lock on the sync object. Doing it this way lets us avoid deadlocks on the lock collection (bad juju).

The Unlock function is also straightforward:

C#
public void Unlock(T name)
{
    lock (lockCollection)
    {
        ReferenceCount obj = null;
        lockCollection.TryGetValue(name, out obj);
        if (obj != null)
        {
            Monitor.Exit(obj);
            if (0 == obj.Release())
            {
                lockCollection.Remove(name);
            }
        }
    }
}

It locks the lockCollection, grabs the sync object, releases the named lock, then removes the sync object if there aren't any other threads holding references to it. This code is a bit more straightforward, since we don't have to do anything janky to avoid dead locks on the lock collection.

The token class is so shockingly simple I'm not even going to paste it here. It takes a reference to the parent NamedLock, and then calls parent.Unlock(name) when disposed.

History

License

This article, along with any associated source code and files, is licensed under The BSD License