I have a problem in a production service which contains a "watchdog" timer used to check whether the main processing job has become frozen (this is related to a COM interop problem which unfortunately can't be reproduced in test).
Here's how it currently works:
- During processing, the main thread resets a
ManualResetEvent
, processes a single item (this shouldn't take long), then sets the event. It then continues to process any remaining items. - Every 5 minutes, the watchdog calls
WaitOne(TimeSpan.FromMinutes(5))
on this event. If the result is false, the service is restarted. - Sometimes, during normal operation, the service is being restarted by this watchdog even though processing takes nowhere near 5 minutes.
The cause appears to be that when multiple items await processing, the time between the Set()
after the first item is processed, and the Reset()
before the second item is processed is too brief, and WaitOne()
doesn't appear to recognise that the event has been set.
My understanding of WaitOne()
is that the blocked thread is guaranteed to receive a signal when Set()
is called, but I assume I'm missing something important.
Note that if I allow a context switch by calling Thread.Sleep(0)
after calling Set()
, WaitOne()
never fails.
Included below is a sample which produces the same behaviour as my production code. WaitOne()
sometimes waits for 5 seconds and fails, even though Set()
is being called every 800 milliseconds.
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);
}
}
The Question
While I understand that calling Set()
closely followed by Reset()
doesn't guarantee that all blocked threads will resume, is it also not guaranteed that any waiting threads will be released?
Set
and another thread callsReset
. callingSet
then immediately callingReset
doesn't sound like a good idea based on the documentation. – RobersonSleep(1)
is no longer required in OS versions after Server 2003. – Denise