C# 为什么等待ManualResetEvent的线程即使在调用Close()时也会继续等待?

C# 为什么等待ManualResetEvent的线程即使在调用Close()时也会继续等待?,c#,.net,multithreading,dispose,C#,.net,Multithreading,Dispose,今天我们惊奇地发现,等待ManualResetEvent的线程即使在事件关闭时也会继续等待该事件。我们希望调用Close()会隐式地向等待的线程发出信号 我们发现这是我们的一些windows服务没有像我们希望的那样快速关闭的原因。我们正在将所有关闭ManualResetEvent引用的Dispose实现更改为先调用Set 谁能解释一下为什么Close没有隐式调用Set?您希望等待线程何时继续等待 下面是我们的测试代码,以演示我们的发现: private static readonly S

今天我们惊奇地发现,等待
ManualResetEvent
的线程即使在事件关闭时也会继续等待该事件。我们希望调用
Close()
会隐式地向等待的线程发出信号

我们发现这是我们的一些windows服务没有像我们希望的那样快速关闭的原因。我们正在将所有关闭
ManualResetEvent
引用的
Dispose
实现更改为先调用
Set

谁能解释一下为什么
Close
没有隐式调用
Set
?您希望等待线程何时继续等待

下面是我们的测试代码,以演示我们的发现:

    private static readonly Stopwatch _timer = Stopwatch.StartNew();

    public static void Test()
    {

        var sync = new ManualResetEvent(false);

        ThreadPool.QueueUserWorkItem(state =>
                                         {
                                             Log("ThreadPool enter, waiting 250ms...");
                                             sync.WaitOne(250);
                                             Log("ThreadPool exit");
                                         });

        Log("Main sleeping 100");
        Thread.Sleep(100);
        Log("Main about to close");
        // sync.Set();      // Is Set called implicitly?  No...
        sync.Close();

        Log("Main waiting for exit 500ms");
        Thread.Sleep(500);
    }

    private static void Log(string text)
    {
        Console.WriteLine("{0:0} {1}", _timer.ElapsedMilliseconds, text);  
    }
当我们运行带有
Set
调用注释的代码时,我们得到了这个

0 Main sleeping 100
0 ThreadPool enter, waiting 250ms...
103 Main about to close
103 Main waiting for exit 500ms
259 ThreadPool exit
0 Main sleeping 100
0 ThreadPool enter, waiting 250ms...
98 Main about to close
98 ThreadPool exit
98 Main waiting for exit 500ms
当我们显式调用
Set
时,我们得到了这个结果

0 Main sleeping 100
0 ThreadPool enter, waiting 250ms...
103 Main about to close
103 Main waiting for exit 500ms
259 ThreadPool exit
0 Main sleeping 100
0 ThreadPool enter, waiting 250ms...
98 Main about to close
98 ThreadPool exit
98 Main waiting for exit 500ms

Close
是一种处理对象的方法(
Close
Dispose
在此类上产生相同的行为)。它不会影响句柄的状态。假设在所有情况下,用户都希望线程在我关闭的句柄上等待继续,这似乎是不合理的。事实上,句柄正在使用这一事实应该表明您首先不应该调用
Close


这不是“为什么不应该隐式调用
Set
”的问题,而是一个概念性的问题:如果调用
Close
,就不应该再关心对象。使用
Set
Reset
控制线程之间的执行流;不要对任何对象调用
Close
(或
Dispose
),除非它们不再使用。

这些同步事件基于Win32等待句柄,
Close()
方法只释放它们(如
Dispose()
),而不发送信号,等待的线程一直在等待。

再加上这一点,将导致难以跟踪竞争条件,即使在OP的示例中,如果sync.Close();在线程池开始执行sync.WaitOne()之前碰巧被调用;我在这整件事上遇到的问题是,WaitHandle被设计为支持线程之间的同步,所以我觉得应该隐式地处理Set on Close和event Wait after Close。不过不用担心,我们创建了一个自定义扩展类,它添加了我们想要的功能。@Sam:我很高兴你找到了解决方案,但问题是,你关于对象生命周期的理念与设计人员的理念不符:仅仅是你关心句柄的状态就意味着你,通过他们的设计,尚未准备好调用
Close