我需要在ManualResetEvent上调用Close()方法吗?

16
我一直在研究.NET Threading,并且正在编写使用ManualResetEvent的代码。我在互联网上找到了很多代码示例。然而,在阅读WaitHandle文档时,我看到了以下内容:

WaitHandle实现了Dispose模式。参见实现Finalize和Dispose以清理非托管资源。

似乎没有任何示例调用它们创建的ManualResetEvent对象的.Close()方法,即使是来自pfxteam博客的漂亮的Recursion and Concurrency文章(编辑 - 我错过了一个using块)。这只是示例的疏忽,还是不需要?我很好奇,因为WaitHandle“封装操作系统特定的对象”,所以可能会出现资源泄漏。
6个回答

22

最近我收到了一段来自《C# 4.0权威指南》(作者:Joseph Albahari, Ben Albahari)的摘录。在第21章:多线程的第834页中,有一个关于如下内容的小节。

释放等待句柄资源

当您使用完一个等待句柄后,可以调用其Close方法以释放操作系统资源。或者,您可以简单地删除所有对等待句柄的引用,并允许垃圾回收器稍后为您完成此任务(等待句柄实现了可处理方式,其中终结器调用Close)。这是仅有几种情况之一,其中依赖于这种备份(可能)是可以接受的,因为等待句柄具有轻量级的操作系统负担(异步委托依赖于正是这种机制来释放它们的IAsyncResult等待句柄)。

应用程序域卸载时,等待句柄会被自动释放。


WaitHandle.Finalize的文档说明在.NET 2.0中不再有实现。您也可以通过反编译查看此内容。WaitHandle不再具有终结器。我不知道为什么,但似乎任何被遗弃的WaitHandle都会泄漏。请参见http://msdn.microsoft.com/en-us/library/vstudio/bb291974(v=vs.90).aspx。 - Djof
5
即使 WaitHandle 类不再拥有 Finalize 方法,但我认为这并不意味着它们会泄漏。相反,清理工作在 SafeHandle 类中处理,而 WaitHandle 持有对其的引用。 - supercat

11

通常,如果一个对象实现了 IDisposable 接口,那么它这样做是有原因的,你应该调用 Dispose(或者视情况调用Close)。在你提到的例子中,ManualResetEvent 被包含在 using 语句中,这将“自动”处理调用 Dispose。在本例中,CloseDispose 是同义词(在大多数提供了 Close 方法的 IDisposable 实现中都是如此)。

示例代码:

using (var mre = new ManualResetEvent(false))
{
   ...
}

扩展为

var mre = new ManualResetEvent(false);
try
{
   ...
}
finally
{
   ((IDispoable)mre).Dispose();
}

2
你会注意到这段代码。
 using (var mre = new ManualResetEvent(false))
 {
    // Process the left child asynchronously
    ThreadPool.QueueUserWorkItem(delegate
    {
        Process(tree.Left, action);
        mre.Set();
    });

    // Process current node and right child synchronously
    action(tree.Data);
    Process(tree.Right, action);

    // Wait for the left child
    mre.WaitOne();
}

使用 'using' 关键字。即使代码抛出异常,它也会在完成时自动调用 dispose 方法。


我在查看那段代码时完全忽略了 using 块。谢谢你指出来。 - Kevin Hakanson

2
如果您正在使用匿名方法的ManualResetEvent,那么它显然是有用的。但正如Sam所提到的,它们经常被传递到工作线程中,然后被设置和关闭。
因此,我认为这取决于您如何使用它的上下文 - MSDN WaitHandle.WaitAll() 代码示例有一个很好的例子。
以下是基于MSDN示例的示例,展示了如何使用using语句创建WaitHandles会出现异常:

System.ObjectDisposedException
"Safe handle has been closed"

const int threads = 25;

void ManualWaitHandle()
{
    ManualResetEvent[] manualEvents = new ManualResetEvent[threads];

    for (int i = 0; i < threads; i++)
    {
        using (ManualResetEvent manualResetEvent = new ManualResetEvent(false))
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(ManualWaitHandleThread), new FileState("filename", manualResetEvent));
            manualEvents[i] = manualResetEvent;
        }
    }

    WaitHandle.WaitAll(manualEvents);
}

void ManualWaitHandleThread(object state)
{
    FileState filestate = (FileState) state; 
    Thread.Sleep(100);
    filestate.ManualEvent.Set();
}

class FileState
{
    public string Filename { get;set; }
    public ManualResetEvent ManualEvent { get; set; }

    public FileState(string fileName, ManualResetEvent manualEvent)
    {
        Filename = fileName;
        ManualEvent = manualEvent;
    }
}

这似乎是一个例子,在其中ManualResetEvent没有调用.Close(),也没有使用using块。我认为工作线程在设置后无法关闭它,因为主线程在WaitHandle.WaitAll(manualEvents)调用中正在使用它。 - Kevin Hakanson
@Kevin 我的意思是,当WaitHandles数组被创建时,它们不能被包装在using语句中,因为我认为它们会在到达时关闭,不过我需要再确认一下。 - Chris S

2

2

我经常使用ManualResetEvent,但我从来没有在单个方法中使用过它——它总是作为类的实例字段存在。因此,using()通常不适用。

如果你有一个类实例字段是ManualResetEvent的实例,请让你的类实现IDisposable接口,并在Dispose()方法中调用ManualResetEvent.Close()。然后在所有使用该类的地方,你需要使用using()或让包含该类的类实现IDisposable并重复这个步骤,再重复......


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接