为啥即使调用 Close(),等待 ManualResetEvent 的线程也会继续等待?

Posted

技术标签:

【中文标题】为啥即使调用 Close(),等待 ManualResetEvent 的线程也会继续等待?【英文标题】:Why do threads waiting on a ManualResetEvent continue waiting even when Close() is called?为什么即使调用 Close(),等待 ManualResetEvent 的线程也会继续等待? 【发布时间】:2010-04-06 16:03:04 【问题描述】:

今天我们惊讶地发现,等待 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

当我们显式调用Set 时,我们会得到这个..

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

【问题讨论】:

【参考方案1】:

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

这不是“为什么不应该隐式调用Set?”的问题,这是一个概念问题:如果你调用Close你应该不再关心对象。使用SetReset控制线程间的执行流程;不要在任何对象上调用Close(或Dispose),包括WaitHandles,直到它们不再被使用。

【讨论】:

此外,它会导致难以跟踪竞争条件,即使在 OP 的示例中,如果 sync.Close(); 也会遇到麻烦。碰巧在线程池开始执行sync.WaitOne();之前被调用 我遇到的问题是 WaitHandle 旨在支持线程之间的同步,所以我觉得 Set on Close 和事件 Wait after Close 应该被隐式处理。不过不用担心,我们创建了一个自定义扩展类来添加我们想要的功能。 @Sam:我很高兴你找到了解决方案,但同样,问题是你关于对象生命周期的理念与设计者的理念不符:事实上,你 关心句柄处于什么状态意味着你,按照他们的设计,还没有准备好打电话给Close【参考方案2】:

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

【讨论】:

以上是关于为啥即使调用 Close(),等待 ManualResetEvent 的线程也会继续等待?的主要内容,如果未能解决你的问题,请参考以下文章

调用 WebSocket.close 时现有等待会发生啥

即使在 connection.close() 调用之后,连接仍处于建立状态

为啥实现Autoclosable接口的类的close方法比Sql连接关闭先调用?

为啥 Oledb Connection.Close() 执行时间太长?

为啥即使使用单个 reducer 也会调用 Partitioner

Async-Await:即使一个错误,如何在多个等待调用中获取数据?