C# 使用块和引用 IDisposable 对象的匿名方法

本文关键字:对象 方法 IDisposable 引用 | 更新日期: 2023-09-27 18:31:20

请考虑以下代码:

using (var mre = new ManualResetEvent(false))
{
     var bgWkr = new BackgroundWorker();
     bgWkr.DoWork += delegate(object sender, DoWorkEventArgs e)
     {
         var mrEvent = e.Argument as ManualResetEvent;
         // some processing...
         mrEvent.WaitOne();
         // broadcast an event
     };
     bgWkr.RunWorkerAsync(mre);
     // some other processing...
     // hook into the same event
     mre.Set();
}

假设生成的工人需要一点时间才能完成。当工作线程完成并等待 ManualResetEvent 时,我们将在一段时间前离开使用块。我假设 mre 在离开使用块时会被关闭(假设它会被处理掉),这至少会引发异常。这是一个安全的假设吗?

这个例子可能不是最好的 ManualResetEvent 的例子,但它是为了说明我们在 using 块中的匿名方法中访问 IDisposable 对象并在退出 using 块后调用匿名方法的情况。是否有某种机制可以固定一次性物品?我不这么认为,但希望得到一些确认,为什么(如果有某种巫毒教在起作用)或为什么不这样做。

干杯

C# 使用块和引用 IDisposable 对象的匿名方法

是的,这段代码是错误的 - 结果并没有真正定义,但它在mrEvent.WaitOne()抛出异常是相当合理的,因为mrEvent几乎可以肯定现在已处置ManualResetEvent。从技术上讲,工作线程有可能全部准备就绪,并且工作线程进行了"一些处理..."比主线程执行"其他一些处理..."的速度更快,但是:我不会依赖它。所以在大多数情况下:mrEvent已经死了。

至于如何避免这种情况:也许这根本不是using的情况。但是,由于工作线程执行WaitOne,因此在主线程执行mre.Set()调用之前,工作线程的WaitOne无法完成 - 因此您可以利用这一点并将using移动到工作线程:

 var mre = new ManualResetEvent(false);
 var bgWkr = new BackgroundWorker();
 bgWkr.DoWork += delegate(object sender, DoWorkEventArgs e)
 {
     using(var mrEvent = e.Argument as ManualResetEvent)
     {
         // some processing...
         mrEvent.WaitOne();
     }
     // broadcast an event
 };
 bgWkr.RunWorkerAsync(mre);
 // some other processing...
 // hook into the same event
 mre.Set();

但是请注意,这提出了一个有趣的问题,即如果主线程在"其他一些处理..."中抛出异常会发生什么 - 永远不会到达对mre.Set()的调用,并且工作线程永远不会退出。您可能希望在finally中执行mre.Set()

 var mre = new ManualResetEvent(false);
 try {
     var bgWkr = new BackgroundWorker();
     bgWkr.DoWork += delegate(object sender, DoWorkEventArgs e)
     {
         using(var mrEvent = e.Argument as ManualResetEvent)
         {
             // some processing...
             mrEvent.WaitOne();
         }
         // broadcast an event
     };
     bgWkr.RunWorkerAsync(mre);
     // some other processing...
 }
 finally {
    // hook into the same event
    mre.Set();
 }

为了回应我的评论(而不是提出问题的答案),我创建了一个类来关闭 ManualResetEvent 一旦完成它,而无需跟踪最后一个线程何时完成使用它。感谢Marc Gravell的想法,一旦WaitOne完成就关闭它。如果其他人需要它,我在这里公开它。

附言我被限制在 .NET 3.5...因此,为什么我不使用ManualResetEventSlim。

干杯

肖恩

public class OneTimeManualResetEvent
{
    private ManualResetEvent _mre;
    private volatile bool _closed;
    private readonly object _locksmith = new object();
    public OneTimeManualResetEvent()
    {
        _mre = new ManualResetEvent(false);
        _closed = false;
    }
    public void WaitThenClose()
    {
        if (!_closed)
        {
            _mre.WaitOne();
            if (!_closed)
            {
                lock (_locksmith)
                {
                    Close();
                }
            }
        }
    }
    public void Set()
    {
        if (!_closed)
            _mre.Set();
    }
    private void Close()
    {
        if (!_closed)
        {
            _mre.Close();
            _closed = true;
        }
    }
}