Как разблокировать потоки, которые вызвали метод WaitOne для объекта AutoResetEvent? - PullRequest
12 голосов
/ 09 мая 2011

Ниже приведен класс с методом 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. Возвращаемое значение равно true, только если не было заблокированных потоков.

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

Ответы [ 3 ]

11 голосов
/ 09 мая 2011

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

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

3 голосов
/ 09 мая 2011

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

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

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

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

1 голос
/ 09 мая 2011

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

...