Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Condition Variables C#/.NET

Tags:

c#

.net

In my quest to build a condition variable class I stumbled on a trivially simple way of doing it and I'd like to share this with the stack overflow community. I was googling for the better part of an hour and was unable to actually find a good tutorial or .NET-ish example that felt right, hopefully this can be of use to other people out there.

like image 749
John Leidegren Avatar asked Mar 27 '13 11:03

John Leidegren


People also ask

What are conditions of variables?

A condition variable indicates an event and has no value. More precisely, one cannot store a value into nor retrieve a value from a condition variable. If a thread must wait for an event to occur, that thread waits on the corresponding condition variable.

Why are condition variables used?

Condition variables are used to wait until a particular condition predicate becomes true. This condition predicate is set by another thread, usually the one that signals the condition. A condition predicate must be protected by a mutex.

Why do condition variables need locks?

The lock is used to enforce mutual exclusion. The condition variables are used as wait queues so that other threads can sleep while the lock is held. Thus condition variables make it so that if a thread can safely go to sleep and be guaranteed that when they wake up they will have control of the lock again.

Does condition variable have a queue?

Condition variables are variables that represent certain conditions and can only be used in monitors. Associated with each condition variable, there is a queue of threads and two operations: condition signal and condition wait.


2 Answers

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);
        }
    }
}

From the outside, this would look incredibly deadlock-ish but the locking protocol for the Monitor will release the lock when you call Monitor.Wait, actually, 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 is 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 used to protect the shared resource, so, if you code that is do work here... needs to access a shared resource you need a additional lock, protecting that resource.

We can leverage the above code 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 like this.

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.

like image 117
3 revs, 2 users 99% Avatar answered Oct 02 '22 13:10

3 revs, 2 users 99%


Use ManualResetEvent

The class that is similar to conditional variable is the ManualResetEvent, just that the method name is slightly different.

The notify_one() in C++ would be named Set() in C#.
The wait() in C++ would be named WaitOne() in C#.

Moreover, ManualResetEvent also provides a Reset() method to set the state of the event to non-signaled.

like image 33
Wong Jia Hau Avatar answered Oct 02 '22 14:10

Wong Jia Hau