Как разблокировать потоки, которые вызвали метод WaitOne для объекта AutoResetEvent?

Ниже приведен класс с методом SomeMethod, который иллюстрирует мою проблему.

class SomeClass
{
    AutoResetEvent theEvent = new AutoResetEvent(false);
    // more member declarations

    public void SomeMethod()
    {
        // some code
        theEvent.WaitOne();
        // more code
    }
}

Метод разработан для обеспечения многопоточности и будет вызываться в разных потоках. Теперь мой вопрос: как можно в любой момент разблокировать все потоки, которые вызвали метод WaitOne для объекта theEvent? Это требование часто возникает в моем проекте, потому что мне нужно иметь возможность корректно останавливать и запускать мою многопоточную программу. Мне кажется, что запустить многопоточную программу довольно просто, но сложно ее остановить.

Вот то, что я пробовал до сих пор, который, очевидно, работает. Но это стандартный подход?

public void UnblockAll()
{
    do
    {
        theEvent.Set();
    } while (theEvent.WaitOne(0));
}

Метод UnblockAll является членом класса SomeClass. Используемая здесь методика основана на документации MSDN метода WaitOne. Я цитирую соответствующую часть документации ниже:

Если millisecondsTimeout равен нулю, метод не блокируется. Он проверяет состояние дескриптора ожидания и немедленно возвращается.

В цикле do.. while я вызываю метод Set. Это освобождает один поток, который может быть заблокирован из-за вызова метода WaitOne (закодированного внутри метода SomeMethod). Затем я проверяю состояние объекта 'theEvent' просто чтобы узнать, сигнализируется ли он. Этот тест выполняется путем вызова перегруженной версии метода WaitOne, который принимает параметр времени ожидания. Аргумент, который я использую при вызове метода WaitOne, равен нулю, что в соответствии с документацией приводит к немедленному возвращению вызова с логическим значением. Если возвращаемое значение равно true, то объект 'theEvent' находился в сигнальном состоянии. Если при вызове метода "WaitOne" в методе "SomeMethod" был заблокирован хотя бы один поток, вызов метода "Set" (закодированный внутри метода "UnblockAll") разблокировал бы его. Следовательно, вызов метода WaitOne в конце оператора do.. while в методе UnblockAll вернул бы false. Возвращаемое значение истинно, только если не было заблокированных потоков.

Верны ли приведенные выше рассуждения и, если они верны, является ли метод стандартным способом решения моей проблемы? Я пытаюсь использовать решение в первую очередь на платформе.net compact-framework 2.0.

3 ответа

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

Вариант 1 - Опрос WaitHandle ,

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

public void SomeMethod()
{
  while (!yourEvent.WaitOne(POLLING_INTERVAL))
  {
    if (IsShutdownRequested())
    {
      // Add code to end gracefully here.
    }
  }
  // Your event was signaled so now we can proceed.
}

Вариант 2 - использовать отдельный WaitHandle для запроса отключения

public void SomeMethod()
{
  WaitHandle[] handles = new WaitHandle[] { yourEvent, shutdownEvent };
  if (WaitHandle.WaitAny(handles) == 1)
  {
    // Add code to end gracefully here.
  }
  // Your event was signaled so now we can proceed.
}

Вариант 3 - Использование Thread.Interrupt

Не путайте это с Thread.Abort, Прерывание потока определенно небезопасно, но прерывание потока совершенно другое. Thread.Interrupt будет "тыкать" встроенные вызовы блокировки, используемые в BCL, включая Thread.Join, WaitHandle.WaitOne, Thread.Sleep, так далее.

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

Я считаю, что классы AutoResetEvent и ManualResetEvent отлично работают для действительно простых сценариев. Каждый раз, когда в требованиях появляется что-то странное, я быстро переключаюсь на более гибкий паттерн ожидания и пульса.

Если вам не нужна очистка, вы можете сделать свои рабочие потоки фоновыми потоками, и тогда они просто остановятся, когда выйдет основной поток.

Вы также можете определить второе значение ManualResetEvent с именем stopRequest и ожидать сигнала от любого события. Однако это может не поддерживаться в компактной среде.

Работает ли прерывание потока для вашего фреймворка?

Другие вопросы по тегам