Condition Variables C#/.NET

It’s actually incredibly simple, once you know about the semantics of lock and Monitor.

But first, you do need an object reference. You can use this, but remember that this is public, in the sense that anyone with a reference to your class can lock on that reference. If you are uncomfortable with this, you can create a new private reference, like this:

readonly object syncPrimitive = new object(); // this is legal

Somewhere in your code where you’d like to be able to provide notifications, it can be accomplished like this:

void Notify()
{
    lock (syncPrimitive)
    {
        Monitor.Pulse(syncPrimitive);
    }
}

And the place where you’d do the actual work is a simple looping construct, like this:

void RunLoop()
{
    lock (syncPrimitive)
    {
        for (;;)
        {
            // do work here...
            Monitor.Wait(syncPrimitive);
        }
    }
}

Yes, this looks incredibly deadlock-ish, but the locking protocol for Monitor is such that it will release the lock during the Monitor.Wait. In fact, it’s a requirement that you have obtained the lock before you call either Monitor.Pulse, Monitor.PulseAll or Monitor.Wait.

There’s one caveat with this approach that you should know about. Since the lock is required to be held before calling the communication methods of Monitor you should really only hang on to the lock for an as short duration as possible. A variation of the RunLoop that’s more friendly towards long running background tasks would look like this:

void RunLoop()
{
    
    for (;;)
    {
        // do work here...

        lock (syncPrimitive)
        {
            Monitor.Wait(syncPrimitive);
        }
    }
}

But now we’ve changed up the problem a bit, because the lock is no longer protecting the shared resource throughout the processing. So, if some of your code in the do work here... bit needs to access a shared resource you’ll need an separate lock managing access to that.

We can leverage the above to create a simple thread-safe producer consumer collection (although .NET already provides an excellent ConcurrentQueue<T> implementation; this is just to illustrate the simplicity of using Monitor in implementing such mechanisms).

class BlockingQueue<T>
{
    // We base our queue on the (non-thread safe) .NET 2.0 Queue collection
    readonly Queue<T> q = new Queue<T>();

    public void Enqueue(T item)
    {
        lock (q)
        {
            q.Enqueue(item);
            System.Threading.Monitor.Pulse(q);
        }
    }

    public T Dequeue()
    {
        lock (q)
        {
            for (;;)
            {
                if (q.Count > 0)
                {
                    return q.Dequeue();
                }
                System.Threading.Monitor.Wait(q);
            }
        }
    }
}

Now the point here is not to build a blocking collection, that also available in the .NET framework (see BlockingCollection). The point is to illustrate how simple it is to build an event driven message system using the Monitor class in .NET to implement conditional variable. Hope you find this useful.

Leave a Comment