C# ManualResetEvent.WaitOne()不使用';如果在Set()之后立即调用Reset(),则返回

C# ManualResetEvent.WaitOne()不使用';如果在Set()之后立即调用Reset(),则返回,c#,multithreading,.net-4.0,manualresetevent,C#,Multithreading,.net 4.0,Manualresetevent,我在生产服务中遇到了一个问题,其中包含一个“看门狗”计时器,用于检查主处理作业是否已冻结(这与COM互操作问题有关,不幸的是,该问题无法在测试中重现) 以下是它目前的工作原理: 在处理过程中,主线程重置,处理单个项目(这不会花费太长时间),然后设置事件。然后它继续处理任何剩余的项目 每5分钟,看门狗就会对该事件调用WaitOne(TimeSpan.frommins(5))。如果结果为false,则重新启动服务 有时,在正常操作过程中,该看门狗会重新启动服务,即使处理过程几乎不需要5分钟 原因

我在生产服务中遇到了一个问题,其中包含一个“看门狗”计时器,用于检查主处理作业是否已冻结(这与COM互操作问题有关,不幸的是,该问题无法在测试中重现)

以下是它目前的工作原理:

  • 在处理过程中,主线程重置,处理单个项目(这不会花费太长时间),然后设置事件。然后它继续处理任何剩余的项目
  • 每5分钟,看门狗就会对该事件调用
    WaitOne(TimeSpan.frommins(5))
    。如果结果为false,则重新启动服务
  • 有时,在正常操作过程中,该看门狗会重新启动服务,即使处理过程几乎不需要5分钟
原因似乎是,当多个项目等待处理时,处理第一个项目后的
Set()
与处理第二个项目前的
Reset()
之间的时间太短,并且
WaitOne()
似乎无法识别事件已设置

我对
WaitOne()
的理解是阻塞的线程是,但我认为我遗漏了一些重要的东西

请注意,如果我在调用
Set()
之后通过调用
Thread.Sleep(0)
来允许上下文切换,
WaitOne()
永远不会失败

下面是一个示例,它产生与我的生产代码相同的行为
WaitOne()
有时会等待5秒并失败,即使每隔800毫秒调用一次
Set()


问题


虽然我知道在调用
Set()
之后紧接着调用
Reset()
不能保证所有被阻止的线程都会恢复,但也不能保证任何等待的线程都会被释放吗?

不,这是根本不正确的代码。当您在如此短的时间内保持MRE设置时,WaitOne()完成的几率是合理的。Windows支持释放在事件中被阻止的线程。但是当线程没有等待时,这将彻底失败。或者调度程序选择另一个线程,一个运行优先级更高且未被阻止的线程。例如,可以是内核线程。MRE不保留已发出信号且尚未等待的“记忆”

睡眠(0)或睡眠(1)都不足以保证等待将要完成,调度程序绕过等待线程的频率没有合理的上限。虽然您可能应该在程序运行时间超过10秒时关闭该程序;)

你需要用不同的方式来做。一种简单的方法是依靠工作人员最终设置事件。因此,请在开始等待之前重置它:

private static void PeriodicWait() {
    Stopwatch stopwatch = new Stopwatch();

    while (true) {
        stopwatch.Restart();
        _handle.Reset();
        bool result = _handle.WaitOne(5000);
        stopwatch.Stop();
        Console.WriteLine("After WaitOne: {0}. Waited for {1}ms", result ? "success" : "failure",
                            stopwatch.ElapsedMilliseconds);
    }
}

private static void PeriodicSignal() {
    while (true) {
        _handle.Set();
        Thread.Sleep(800);   // Simulate work
    }
}
你不能像这样“脉冲”操作系统事件

在其他问题中,任何对操作系统句柄执行阻塞等待的操作系统线程都可能被内核模式APC临时中断;APC完成后,线程继续等待。如果脉冲发生在中断期间,线程就看不到它。这只是一个如何遗漏“脉冲”的示例(详见第231页)

顺便说一句,这确实意味着
PulseEvent
win32api是可用的

在具有托管线程的.NET环境中,丢失脉冲的可能性更大。垃圾收集等


在您的情况下,我会考虑切换到<代码> AutoReTebug事件/代码>,它是由工作进程重复的<代码> SET/CODE >,并且在每次代码< >等待> /代码>完成时,由看门狗进程自动重置。你可能想“驯服”看门狗,让它每隔一分钟左右检查一次。

Thread.Sleep(0)不一定会导致上下文切换——它只会切换到相同优先级的线程。睡眠(1)将切换到不同优先级的线程,如果有线程正在等待。请参阅文档中的说明“如果在所有线程恢复执行之前调用了Reset方法,那么剩余的线程将再次阻塞”。它还详细说明了在大致相同的条件下,autoreset事件不保证解除线程阻塞()不,它基本上是定量工作的。它不能从用户线程中窃取时间来安排另一个线程。它以量子频率分配和调度线程。如果你在量子内切换一个信号,它可能永远不会“看到”它。这是以前出现过的。似乎操作系统的状态机坏了。Set使线程准备就绪,但在实际运行之前,似乎不会将其从MRE等待队列中删除。如果在线程仍处于就绪状态且尚未运行时进行重置,则会再次将其设置为“等待”。我不认为这种行为是正常的,但事实就是如此。IMHO,在MRE上调用set应该使所有当前正在等待的线程都准备就绪,并将它们从MRE等待队列中删除,以便后续重置不能将它们的状态从就绪变回等待。我想不出OS/MRE以这种方式运行的任何原因。这太傻了。
private static void PeriodicWait() {
    Stopwatch stopwatch = new Stopwatch();

    while (true) {
        stopwatch.Restart();
        _handle.Reset();
        bool result = _handle.WaitOne(5000);
        stopwatch.Stop();
        Console.WriteLine("After WaitOne: {0}. Waited for {1}ms", result ? "success" : "failure",
                            stopwatch.ElapsedMilliseconds);
    }
}

private static void PeriodicSignal() {
    while (true) {
        _handle.Set();
        Thread.Sleep(800);   // Simulate work
    }
}