【发布时间】:2013-03-08 22:34:04
【问题描述】:
我在包含“看门狗”计时器的生产服务中遇到问题,该计时器用于检查主处理作业是否已冻结(这与 COM 互操作问题有关,遗憾的是无法在测试中重现)。
这是它目前的工作方式:
- 在处理期间,主线程重置
ManualResetEvent,处理单个项目(这应该不会花费很长时间),然后设置事件。然后它会继续处理任何剩余的项目。 - 每隔 5 分钟,看门狗就此事件调用
WaitOne(TimeSpan.FromMinutes(5))。如果结果为 false,则重新启动服务。 - 有时,在正常操作期间,该看门狗会重新启动服务,即使处理时间不到 5 分钟。
原因似乎是当多个项目等待处理时,处理第一个项目之后的Set()与处理第二个项目之前的Reset()之间的时间太短,而WaitOne()没有t 似乎认识到该事件已设置。
我对@987654332@ 的理解是被阻塞的线程是guaranteed to receive a signal when Set() is called,但我认为我遗漏了一些重要的东西。
请注意,如果我在调用Set() 之后通过调用Thread.Sleep(0) 来允许上下文切换,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() 并不能保证所有阻塞的线程都会恢复,但是否也不能保证任何等待的线程都会被释放? p>
【问题讨论】:
-
Thread.Sleep(0) 不一定会导致上下文切换——它只会切换到相同优先级的线程。 Thread.Sleep(1) 将切换到不同优先级的线程,如果有等待的话。见bluebytesoftware.com/blog/…
-
文档还说“如果在所有线程恢复执行之前调用 Reset 方法,则剩余线程再次阻塞”msdn.microsoft.com/en-us/library/ksb7zs2x(v=vs.95).aspx。它还详细说明了自动重置事件不能保证在大致相同的条件下解锁线程 (msdn.microsoft.com/en-us/library/…)
-
不,它基本上适用于量子。它不能从用户线程中窃取时间来安排另一个线程。它以量子频率分配和调度线程。如果您在该量子范围内切换信号,它可能永远不会“看到”它。
-
这个以前出现过。好像操作系统状态机坏了。 Set 使线程/s 准备好,但似乎不会将它们从 MRE 等待队列中删除,直到它们真正开始运行。如果在线程仍准备就绪且尚未运行时发生重置,则再次将其设置回“等待”。我不认为这种行为是理智的,但事实就是如此。
-
恕我直言,在 MRE 上调用 set 应该使所有当前等待的线程准备好并将它们从 MRE 等待队列中删除,以便后续重置无法将它们的状态从准备好更改回等待。我想不出 OS/MRE 的行为方式有任何原因。这很愚蠢。
标签: c# multithreading .net-4.0 manualresetevent