EventWaitHandle - разница между WaitAny() и WaitOne()

У меня есть 3 потока, два "рабочих" и один "менеджер". Нити "Рабочие" Ожидают EventWaitHandle, что поток "менеджера" будет сигнализировать EventWaitHandle, после чего они увеличивают свои счетчики. Единственное различие между этими "рабочими" потоками заключается в том, что один использует EventWaitHandle.WaitAny(), а другой использует EventWaitHandle.WaitOne().

вот код:

class Program
{
    static void Main(string[] args)
    {
        MultiThreadedJobs multyThreadedJobs = new MultiThreadedJobs();
        multyThreadedJobs.Start();

        Console.ReadLine();

        multyThreadedJobs.Stop();
    }
}

class MultiThreadedJobs : IDisposable
{
    private EventWaitHandle syncEvent;
    private EventWaitHandle[] syncEventsArray;

    private Thread managerThread;
    private Thread firstWorkerThread;
    private Thread secondWorkerThread;

    private volatile bool running = false;


    public MultiThreadedJobs() // Ctor
    {
        syncEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "JobsSyncEvent");

        syncEventsArray = new EventWaitHandle[1];
        syncEventsArray[0] = syncEvent;

        managerThread = new Thread(ManagerThreadMethod);
        firstWorkerThread = new Thread(FirstWorkerThreadMethod);
        secondWorkerThread = new Thread(SecondWorkerThreadMethod);
    }

    public void Start()
    {
        running = true;

        managerThread.Start();
        firstWorkerThread.Start();
        secondWorkerThread.Start();
    }

    public void Stop()
    {
        running = false;
    }

    private void ManagerThreadMethod() // Manager Thread
    {
        while (running)
        {
            Thread.Sleep(1000);
            syncEvent.Set();
        }
    }

    private void FirstWorkerThreadMethod() // Worker Thread
    {
        int counter = 0;
        while (running)
        {
            syncEvent.WaitOne();
            counter++;
        }
    }

    private void SecondWorkerThreadMethod() // Worker Thread
    {
        int counter = 0;
        while (running)
        {
            EventWaitHandle.WaitAny(syncEventsArray);
            counter++;
        }
    }

    public void Dispose()
    {
        syncEvent.Close();
    }
}

Проблема в том, что только второй рабочий поток с EventWaitHandle.WaitAny() всегда захватывает Событие и голодает первый рабочий поток. вместо 50/50 для каждого из них.

Ответы

Ответ 1

Вы ищете решение очень распространенной проблемы в разработке программного обеспечения, проблема производителя-потребителя. Связанная статья Википедии содержит приличную информацию об этом, в частности, показано, как сделать это неправильно.

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

Пример кода слишком искусственен, чтобы предложить хорошую альтернативу. Блокировка низкого уровня может быть реализована классом ReaderWriterLock/Slim. Класс, который особенно хорошо подходит для решения проблем производителей/потребителей, - это класс .NET BlockingCollection. Поддержка произвольного количества потоков производителей и потребителей и обеспечение дросселирования для обеспечения того, чтобы программа не взорвалась, когда потребители не могут идти в ногу с производителями. Вы можете переписать свой образец, используя поддельный "токен", который вы передаете от производителя к потребительским потокам. A BlockingColletion<bool> выполняет задание.

Ответ 2

Класс WaitHandle позволяет клиентам совершать асинхронный вызов и ждать: один веб-сервис XML (WaitHandle.WaitOne), первая из многих веб-служб XML (WaitHandle.WaitAny) или все многие веб-службы XML (WaitHandle.WaitAll), чтобы возвращать результаты. Если вы хотите обработать результаты по мере их поступления, вы можете использовать метод WaitHandle.WaitAny. Этот метод укажет, что одна из операций завершена и идентифицирует завершенную операцию.

Оба метода являются допустимыми. И в зависимости от переданных параметров реализация меняется. Например, метод WaitHandle.WaitAny(WaitHandle [], Int32, Boolean) ожидает, что любой из элементов в указанном массиве получит сигнал, используя 32-разрядное целое число со знаком измерить временной интервал и указать, следует ли выйти из домена синхронизации до ожидания.

WaitHandle.WaitOne Метод (Int32, Boolean) при переопределении в производном классе блокирует текущий поток пока текущий WaitHandle не получит сигнал, используя 32-разрядное целое число со знаком для измерения интервала времени и указания как выйти из домена синхронизации до ожидания.