У меня есть проблема в производственной службе, которая содержит «сторожевой» таймер, используемый для проверки того, не зависло ли основное задание обработки (это связано с проблемой COM-взаимодействия, которую, к сожалению, нельзя воспроизвести в тесте).
Вот как это работает в настоящее время:
- Во время обработки основной поток сбрасывает
ManualResetEvent
, обрабатывает один элемент (это не должно занять много времени), затем устанавливает событие. Затем он продолжает обрабатывать все оставшиеся элементы. - Каждые 5 минут сторожевой таймер вызывает
WaitOne(TimeSpan.FromMinutes(5))
по этому событию. Если результат ложный, служба перезапускается. - Иногда во время нормальной работы служба перезапускается этим сторожевым таймером, хотя обработка не занимает и пяти минут.
Причина, по-видимому, заключается в том, что когда несколько элементов ожидают обработки, время между Set()
после обработки первого элемента и Reset()
перед обработкой второго элемента слишком короткое, а WaitOne()
, похоже, не распознает, что событие было выполнено. задавать.
Насколько я понимаю WaitOne()
, заблокированный поток гарантированно получит сигнал, когда Set()
вызывается, но я предполагаю, что упускаю что-то важное.
Обратите внимание, что если я разрешаю переключение контекста, вызывая Thread.Sleep(0)
после вызова Set()
, WaitOne()
никогда не завершается ошибкой.
Ниже приведен пример, который обеспечивает то же поведение, что и мой производственный код. WaitOne()
иногда ждет 5 секунд и завершается ошибкой, даже если Set()
вызывается каждые 800 миллисекунд.
private static ManualResetEvent _handle;
private static void Main(string[] args)
{
_handle = new ManualResetEvent(true);
((Action) PeriodicWait).BeginInvoke(null, null);
((Action) PeriodicSignal).BeginInvoke(null, null);
Console.ReadLine();
}
private static void PeriodicWait()
{
Stopwatch stopwatch = new Stopwatch();
while (true)
{
stopwatch.Restart();
bool result = _handle.WaitOne(5000, false);
stopwatch.Stop();
Console.WriteLine("After WaitOne: {0}. Waited for {1}ms", result ? "success" : "failure",
stopwatch.ElapsedMilliseconds);
SpinWait.SpinUntil(() => false, 1000);
}
}
private static void PeriodicSignal()
{
while (true)
{
_handle.Reset();
Console.WriteLine("After Reset");
SpinWait.SpinUntil(() => false, 800);
_handle.Set();
// Uncommenting either of the lines below prevents the problem
//Console.WriteLine("After Set");
//Thread.Sleep(0);
}
}
Вопрос
Хотя я понимаю, что вызов Set()
, за которым следует Reset()
, не гарантирует, что все заблокированные потоки будут возобновлены, не гарантируется ли также и то, что любые ожидающие потоки будут освобождены?
Set
, а другой поток вызываетReset
. вызовSet
, а затем немедленный вызовReset
не кажется хорошей идеей, основываясь на документации. - person Peter Ritchie   schedule 20.03.2013Sleep(1)
больше не требуется в версиях ОС после Server 2003. - person Simon MᶜKenzie   schedule 24.04.2014