gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-30 00:00:04 25 4
gpt4 key购买 nike

我在生产服务中遇到一个问题,它包含一个“看门狗”计时器,用于检查主处理作业是否已卡住(这与 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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com