Как разблокировать потоки, которые вызвали метод 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. Я цитирую соответствующую часть приведенной ниже документации:

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

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

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

Ответ 1

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

Вариант 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 и т.д.

Ответ 2

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

Я нахожу, что классы AutoResetEvent и ManualResetEvent отлично работают для действительно простых сценариев. В любое время, когда есть что-то странное в отношении требований, я быстро переключаюсь на более гибкий шаблон Wait And Pulse.

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

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

Ответ 3

Работает ли thread abort для вашей структуры?