代码之家  ›  专栏  ›  技术社区  ›  Jeff Moser

正确通知所有侦听器系统范围内的手动重置事件,然后立即重置该事件。

  •  6
  • Jeff Moser  · 技术社区  · 15 年前

    我通过执行以下操作创建了一个系统范围的手动重置事件:

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

    多个进程创建此事件(例如,它在它们之间共享)。它用于在更新某些内容时通知。

    我希望能够设置此事件,以便对等待它的所有进程发出信号,然后立即重置它,以便阻止随后对事件的等待。

    如果我做了

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

    它有时会通知所有的监听过程。

    如果我做了

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

    更多进程得到通知(我假设这会发生,因为调度程序有机会运行)。

    如果我这样做

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

    然后一切似乎都很顺利,所有流程(如~8)都会得到一致的通知。我不喜欢用“魔法号码”来打睡眠电话。

    在其他进程中,是否有更好的方法通知所有侦听器某个事件已发生,以便在通知时侦听该事件的每个人都接收到该事件信号,然后立即重置该事件,以便任何其他侦听该事件的人都将阻止该事件?

    更新: 在这里,信号灯似乎并不适合,因为事件的侦听器数量可能随时间而变化。事先不知道偶数需要通知多少听众。

    3 回复  |  直到 10 年前
        1
  •  2
  •   Spencer Ruport    15 年前

    您使用的EventWaitHandle类不正确。重置事件不应用于向多个线程发出信号。相反,您需要为每个线程创建一个重置事件,然后当您准备好了,在它们之间循环并使用set()。主线程不应调用reset()方法。可以说,每根线都应该负责关闭它们后面的门。

    下面是一个基本示例:

    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;
        }
    }
    
        2
  •  3
  •   Simon Mourier    10 年前

    我也有同样的问题,令人惊讶的是,在网络上找不到任何好的解决方案来解决这种松散耦合/失火和忘记/多个侦听器类型的事件,所以这里是我想到的。

    请注意解决方案的超时时间介于 Set() Reset() 调用还存在争用条件问题(事实上,它依赖于任意超时值):如果发布服务器在这些调用之间被终止,则所有侦听器都将永远看到事件设置(除非发布服务器再次激活)。

    所以要求是:

    • 有一个发布者(尽管代码中没有真正强制执行)
    • 在0和n之间可以有任意数量的侦听器(在同一进程或其他进程中)(编译二进制文件后,n就固定了)。
    • 听众可以随心所欲地来来去,而不会打扰出版商。
    • 出版商可以随心所欲地来来去,而不会打扰听众。

    诀窍是使用自动设置事件,因为它们不存在争用条件问题,而是为每个侦听器定义一个。我们事先不知道侦听器的数量,但我们可以固定最大数量的侦听器(上面描述的“n”):

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

    以下是向所有潜在侦听器引发事件的发布者代码:

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

    下面是接收事件通知的侦听器代码:

    ...
    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");
    }
    
        3
  •  0
  •   casperOne    15 年前

    此处使用的同步类型不正确。您应该使用信号量类来代替事件,使用您希望允许的同时访问的数量。

    您可能还需要两个信号量,第二个信号量用于触发事件以进行检查的代码(响应事件的代码将保持锁定),以防您不希望快速连续发生两个事件,并让一段代码进入另一个事件的尾部。