Правильно уведомить всех слушателей о событии ручного сброса в масштабе всей системы, а затем немедленно сбросить его - PullRequest
6 голосов
/ 09 января 2009

У меня есть системное событие ручного сброса, которое я создаю, выполнив следующее:

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 ]

3 голосов
/ 31 октября 2013

У меня была та же проблема, и, к удивлению, я не смог найти хорошего решения в Интернете для этого типа событий со свободным соединением / огонь и забыть / несколько слушателей, так что вот что я придумал.

Обратите внимание, что решение с тайм-аутом между вызовами Set() и Reset() имеет также проблему состояния гонки (помимо того факта, что оно зависит от произвольного значения тайм-аута): если издатель погибает между этими вызовами, тогда все слушатели увидят событие как заданное навсегда (если только издатель снова не начнет работу).

Итак, требование:

  • есть один издатель (хотя в коде это не реализовано)
  • может быть любое количество слушателей (в том же процессе или в других процессах), от 0 до N (N фиксируется после компиляции двоичных файлов).
  • слушатели могут приходить и уходить когда угодно, не мешая издателю
  • Издатель может приходить и уходить когда угодно, не мешая слушателям

Хитрость в том, чтобы использовать события AutoReset, потому что они не имеют проблем с состоянием гонки, но определяют по одному на слушателя. Мы не знаем количество слушателей заранее, но мы можем установить максимальное количество слушателей ('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");
}
2 голосов
/ 10 января 2009

Вы используете класс 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;
    }
}
0 голосов
/ 10 января 2009

Вы используете неправильный тип синхронизации здесь. Вместо события вы должны использовать класс Semaphore с количеством одновременных обращений, которые вы хотите разрешить.

Возможно, вы также захотите иметь два семафора, второй для кода, который запускает событие, чтобы проверить (что код, отвечающий на событие, будет удерживать блокировки) в случае, если вы не хотите иметь два события в Быстрая последовательность и один раздел кода включается в хвост другого события.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...