Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Properly notifying all listeners of a system wide named manual reset event and then immediately resetting it

I have a system-wide manual reset event that I create by doing the following:

EventWaitHandle notifyEvent = new EventWaitHandle(false, EventResetMode.ManualReset, notifyEventName, out createdEvent);

Several processes create this event (e.g. it is shared amongst them). It is used for notifying when something gets updated.

I'd like to be able to set this event so that all of processes waiting on it are signaled and then immediately reset it so that subsequent Waits on the event are blocked.

If I do a

notifyEvent.Set();
notifyEvent.Reset();

It will sometimes notify all listening processes.

If I do a

notifyEvent.Set();
Thread.Sleep(0);
notifyEvent.Reset();

More processes get notified (I assumed this would happen since the scheduler has a chance to run).

And if I do

notifyEvent.Set();
Thread.Sleep(100);
notifyEvent.Reset();

Then everything seems to work out fine and all processes (e.g. ~8) get notified consistently. I don't like the use of a "magic number" for the Sleep call.

Is there a better way to notify all listeners of an OS event in other processes that an event has occurred so that everyone listening to it at the time of notification receive the event signal and then immediately reset the event so that anyone else that goes to listen to the event will block?

UPDATE: A Semaphore doesn't seem to be a good fit here since the number of listeners to the event can vary over time. It is not known in advance how many listeners there will be when an even needs to be notified.

like image 737
Jeff Moser Avatar asked Jan 09 '09 20:01

Jeff Moser


2 Answers

You're using the EventWaitHandle class incorrectly. A reset event shouldn't be used to signal multiple threads. Instead, you need to create a reset event for each thread and then when you're ready loop through them all and use Set(). The master thread should not be calling the Reset() method. Each thread should be responsible for closing the gate behind them so to speak.

Here's a basic example:

static class Program
{
    static void Main()
    {
        List<ThreadState> states = new List<ThreadState>();
        ThreadState myState;
        Thread myThread;
        string data = "";

        for (int i = 0; i < 4; i++)
        {
            myThread = new Thread(Work);
            myState = new ThreadState();
            myState.gate = new EventWaitHandle(false, EventResetMode.ManualReset);
            myState.running = true;
            myState.index = i + 1;
            states.Add(myState);
            myThread.Start(myState);
        }

        Console.WriteLine("Enter q to quit.");

        while (data != "q")
        {
            data = Console.ReadLine();
            if (data != "q")
                foreach (ThreadState state in states)
                    state.gate.Set();
        }

        foreach (ThreadState state in states)
        {
            state.running = false;
            state.gate.Set();
        }

        Console.WriteLine("Press any key to quit.");
        Console.ReadKey();
    }

    static void Work(Object param)
    {
        ThreadState state = (ThreadState)param;
        while (state.running)
        {
            Console.WriteLine("Thread #" + state.index + " waiting...");
            state.gate.WaitOne();
            Console.WriteLine("Thread #" + state.index + " gate opened.");
            state.gate.Reset();
            Console.WriteLine("Thread #" + state.index + " gate closed.");
        }
        Console.WriteLine("Thread #" + state.index + " terminating.");
    }

    private class ThreadState
    {
        public int index;
        public EventWaitHandle gate;
        public bool running;
    }
}
like image 23
Spencer Ruport Avatar answered Nov 10 '22 04:11

Spencer Ruport


I had the same problem and surprisingly couldn't find any good solution on the web for this loose-coupled / fire and forget / multiple listeners type of event, so here is what I came up with.

Note the solution with the timeout between Set() and Reset() calls has also a race-condition issue (beyond the fact it relies on an arbitrary timeout value): if the publisher gets killed between these calls, then all the listeners will see the event as set forever (unless the publisher gets live again).

So the requirement is:

  • there is one publisher (although it's not really enforced in the code)
  • there can be any number of listeners (in the same process or in other processes), between 0 and N (N is fixed once the binaries are compiled).
  • listeners can come and go as they want without disturbing the publisher
  • publisher can come and go as it wants without disturbing the listeners

The trick is to use AutoReset events because they don't have race condition issues, but define one per listener. We don't know the number of listeners beforehand but we can fix a maximum number of listeners ('N' described above):

const int MAX_EVENT_LISTENERS = 10;
const string EVENT_NAME = "myEvent_";

Here is the publisher code to raise the event to all potential listeners:

public static void RaiseEvent()
{
    for (int i = 0; i < MAX_EVENT_LISTENERS; i++)
    {
        EventWaitHandle evt;
        if (EventWaitHandle.TryOpenExisting(EVENT_NAME + i, out evt))
        {
            evt.Set();
            evt.Dispose();
        }
    }
}

Here is the listener code to get notified of the event:

...
EventWaitHandle evt = GetEvent();
do
{
    bool b = evt.WaitOne();
    // event was set!
}
while (true);
....

// create our own event that no other listener has
public static EventWaitHandle GetEvent()
{
    for (int i = 0; i < MAX_EVENT_LISTENERS; i++)
    {
        bool createdNew;
        EventWaitHandle evt = new EventWaitHandle(false, EventResetMode.AutoReset, EVENT_NAME + i, out createdNew);
        if (createdNew)
            return evt;

        evt.Dispose();
    }
    throw new Exception("Increase MAX_EVENT_LISTENERS");
}
like image 90
Simon Mourier Avatar answered Nov 10 '22 04:11

Simon Mourier