Non-Zero Thread Latch

No Comments July 24, 2014

Isn't it great when something you wrote years ago suddenly becomes useful again...

If you're in a situation, as a colleague of mine was this morning, where you are spinning up a number of threads to do some work and you need to wait for all of them to complete before moving on, this simple class might be useful:

public class NonZeroLatch
{
    private static int _remainingEvents;
    private static readonly EventWaitHandle _eventWaitHandle = new ManualResetEvent(true);

    public NonZeroLatch()
    {
        _remainingEvents = 0;
    }

    public void Increment()
    {
        if (Interlocked.Increment(ref _remainingEvents) == 1)
        {
            _eventWaitHandle.Reset();
        }
    }

    public void Decrement()
    {
        if (Interlocked.Decrement(ref _remainingEvents) == 0)
        {
            _eventWaitHandle.Set();
        }
    }

    public void Wait()
    {
        _eventWaitHandle.WaitOne();
    }
}

It's a very simple implementation, but quite useful at times. An illustrative example of how you'd use this class is:

var random = new Random();
var latch = new NonZeroLatch();

for (int i = 0; i < 10; i++) {
    int j = i;
    latch.Increment();

    new Task(() => {
        Console.WriteLine("Starting thread: {0}", j);
        Thread.Sleep(TimeSpan.FromSeconds(random.Next(1, 5)));
        Console.WriteLine("Finishing thread: {0}", j);
        latch.Decrement();
    }).Start();
}

latch.Wait();
Console.WriteLine("All threads finished");

Notice that the latch counter is incremented outside the Task instance, whereas it is decremented inside the body of the Task.

The CountdownEvent class was introduced in .NET 4.0 and solves a similar problem, but would require that you know how many threads you are going to spin up in advance.

var threads = 10;
var random = new Random();
var cde = new CountdownEvent(threads);

for (int i = 0; i < threads; i++) {
    int j = i;

    new Task(() => {
        Console.WriteLine("Starting thread: {0}", j);
        Thread.Sleep(TimeSpan.FromSeconds(random.Next(1, 5)));
        Console.WriteLine("Finishing thread: {0}", j);
        cde.Signal();
    }).Start();
}

cde.Wait();
Console.WriteLine("All threads finished");

Update

As the estimable Richard Blewett has pointed out to me, the class could (and should) be made more lightweight by using ManualResetEventSlim instead of the older ManualResetEvent; proof that you blow the dust off an old function, you should check to see if it is fit for purpose.

The updated version would therefore be:

public class NonZeroLatch
{
    private static int _remainingEvents;
    private static readonly ManualResetEventSlim _eventWaitHandle = new ManualResetEventSlim(true);

    public NonZeroLatch()
    {
        _remainingEvents = 0;
    }

    public void Increment()
    {
        if (Interlocked.Increment(ref _remainingEvents) == 1)
        {
            _eventWaitHandle.Reset();
        }
    }

    public void Decrement()
    {
        if (Interlocked.Decrement(ref _remainingEvents) == 0)
        {
            _eventWaitHandle.Set();
        }
    }

    public void Wait()
    {
        _eventWaitHandle.Wait();
    }
}

No Comments