我在生产服务中遇到问题,其中包含一个“看门狗”计时器,用于检查主处理作业是否已冻结(这与 COM 互操作问题有关,遗憾的是无法在测试中重现)。
以下是它目前的工作方式:
- 在处理期间,主线程重置 a
ManualResetEvent
,处理单个项目(这不应该花费很长时间),然后设置事件。然后它继续处理任何剩余的项目。 - 每 5 分钟,看门狗就会调用
WaitOne(TimeSpan.FromMinutes(5))
此事件。如果结果为假,则重新启动服务。 - 有时,在正常操作期间,该看门狗会重新启动服务,即使处理时间不到 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()
并不能保证所有阻塞的线程都会恢复,但是否也不能保证任何等待的线程都会被释放?