Multi-Process Synchronization with Named Events

Since I am in between real projects right now, I have had some time to work on some side projects.  As I was working on some threading in one of these projects, I noticed that there was a new class in the System.Threading namespace that had not been there in .NET 1.1 ...  EventWaitHandle.  I was curious to see what this new class was.

Background Info:

Windows exposes several types of synchronization primitives (Mutexes, Events, etc.), but in .NET 1.1 we were limited in our use of them.  Sure, we had AutoResetEvent and ManualResetEvent, but the framework did not expose the "named" version of these.  In case you were wondering, the "named" version allows us to share an event between two (or more) processes.  The name makes it easy for multiple processes to say "give me 'EventA' "!!  So before 2.0, we had pretty good support for synchronization within a process, but not very good support across processes.  Yes, in .NET 1.1, we could get to this functionality using PInvoke, but it could get complicated quickly because you can attach security descriptors to an event.  And anyone who has tried to PInvoke to set some ACLs can tell you that security descriptor is a beast that you don't want to be playing with in C#.  Fortunately this has been remedied in the 2.0 framework, and the Access Control Lists are now 1st class .NET objects.  I am going to go out on a limb and speculate that this is why named events were not included in the first version of the framework.

Back on Track:

"Great!" you say, but what the heck does this have to do with EventWaitHandle?  Well, EventWaitHandle is .NET 2.0's implementation of events -- both named and unnamed.  You can see that ManualResetEvent and AutoResetEvent now inherit from EventWaitHandle.

So, let's get started and see what we can create with this.  In this example, WorkerA does some work, which notifies WorkerB who begins some work, which notifies WorkerC to finish up some work.  You can see that we are creating a simple pipeline.  (A real pipeline would keep working, but ours just exits after one iteration).

Let's start by providing some tools for these events to get access to the events.

public static class NamedEvents

{

    public static EventWaitHandle OpenOrCreate(string name, bool initialState, EventResetMode mode)

    {

        EventWaitHandle ewh = null;

        try

        {

            ewh = EventWaitHandle.OpenExisting(name);

        }

        catch (WaitHandleCannotBeOpenedException)

        {

            //Handle does not exist, create it.

            ewh = new EventWaitHandle(initialState, mode, name);

        }

        return ewh;

    }

    public static EventWaitHandle OpenOrWait(string name)

    {

        EventWaitHandle ewh = null;

        while (null == ewh)

        {

            try

            {

                ewh = EventWaitHandle.OpenExisting(name);

            }

            catch (WaitHandleCannotBeOpenedException)

            {

                Thread.Sleep(50);

            }

        }

        return ewh;

    }

}

We've provided the tools to create an event if it doesn't already exist or open it if it does, and to open an existing event or wait for it to be created.  Unfortunately there is not a good way to "test" if an event exists, so we just have to catch WaitHandleCannotBeOpenedException.  Here is our code for WorkerB, though the other workers are almost identical except that they set and wait on different events.

static void Main(string[] args)

{

EventWaitHandle completedA = NamedEvents.OpenOrWait("CompletedA");

EventWaitHandle completedB = NamedEvents.OpenOrCreate("CompletedB", false, EventResetMode.ManualReset);

EventWaitHandle pipelineDone = NamedEvents.OpenOrWait("PipelineDone");

Console.WriteLine("{0} Initialized", Process.GetCurrentProcess().ProcessName);

completedA.WaitOne();

for (int i = 0; i < 10; i++)

{

Thread.Sleep(250);

Console.WriteLine("{0} Working: {1:hh-mm-ss.ffff}", Process.GetCurrentProcess().ProcessName, DateTime.UtcNow);

}

Console.WriteLine("{0} Done", Process.GetCurrentProcess().ProcessName);

completedB.Set();

//wait until the whole pipeline is done.

pipelineDone.WaitOne();

completedB.Close();

Console.WriteLine("{0} Exiting", Process.GetCurrentProcess().ProcessName);

}

In the output, we can see that B waits for A to finish, and C waits for B to finish.

Now, you know how to use named events!!

BlogThreads.zip