c# - 如果在 Set() 之后立即调用 Reset(),则 ManualResetEvent.WaitOne() 不会返回

标签 c# multithreading .net-4.0 manualresetevent

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

这是它目前的工作方式:

  • 在处理过程中,主线程重置一个ManualResetEvent ,处理单个项目(这不会花很长时间),然后设置事件。然后它会继续处理任何剩余的项目。
  • 每隔 5 分钟,看门狗就此事件调用 WaitOne(TimeSpan.FromMinutes(5))。如果结果为假,则重新启动服务。
  • 有时,在正常操作期间,该看门狗会重新启动该服务,即使处理时间远不及 5 分钟。

原因似乎是当多个项目等待处理时,第一个项目处理后的 Set() 与第一个项目之前的 Reset() 之间的时间第二项的处理时间太短,WaitOne() 似乎没有识别出事件已设置。

我对WaitOne()的理解是阻塞的线程是guaranteed to receive a signal when Set() is called ,但我想我遗漏了一些重要的东西。

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

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

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);
    }
}

output of the above code


问题

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

最佳答案

不,这是根本上损坏的代码。当您将 MRE 设置保持这么短的时间时,WaitOne() 完成的可能性只有合理。 Windows 倾向于释放因事件而阻塞的线程。但是当线程不等待时,这将彻底失败。或者调度程序选择另一个线程,一个以更高优先级运行并且也未被阻塞的线程。例如,可以是内核线程。 MRE 不会保留已收到信号但尚未等待的“内存”。

无论是 Sleep(0) 还是 Sleep(1) 都不足以保证等待将要完成,调度程序绕过等待线程的频率没有合理的上限.尽管您可能应该在程序运行时间超过 10 秒时关闭它 ;)

您需要以不同的方式执行此操作。一种简单的方法是依靠 worker 最终设置事件。所以在你开始等待之前重置它:

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
    }
}

关于c# - 如果在 Set() 之后立即调用 Reset(),则 ManualResetEvent.WaitOne() 不会返回,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15513262/

相关文章:

c# - 避免在 EF 中插入时发生键冲突

c# - 给定 BNF 语法输出 C# 的解析器生成器?

c++ - 如何获取进程的主线程 ID(通过其 ID 知道)?

multithreading - 在这种情况下什么时候需要调用 CoInitialize() ?

asp.net - 双数据绑定(bind)级联 DropDownList 到 FormView 中的两个 SqlDataSource

C# 将 XML 数据存入字典

c# - RestSharp 接受 header 更改

multithreading - 简化 VCL 线程包装器代码

wcf - 如何将 Json.Net 设置为 WCF REST 服务的默认序列化程序

c# - 是否可以在面向 4.0 的项目中引用面向 .net 4.5 的程序集?