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

时间:2013-09-13 08:20:54

标签: c# delegates idisposable using anonymous-methods

请考虑以下代码:

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块后调用匿名方法的情况。是否存在一些保持一次性物体的机制?我不相信,但想要确认为什么(如果工作中有某种伏都教)或为什么不这样做。

干杯,

2 个答案:

答案 0 :(得分:1)

是的,这段代码错了 - 结果没有真正定义,但是在mrEvent.WaitOne()抛出异常是非常合理的,因为mrEvent几乎肯定是现在 - 处置ManualResetEvent。从技术上讲,工作线程有可能已经准备好了,而工作线程做的“一些处理......”比主线程执行“其他一些处理......”更快,但是:我不会依赖在上面。所以在大多数情况下:mrEvent已经死了。

至于如何避免这种情况:也许这根本不是using的情况。但是,由于工作线程执行WaitOne,工作线程的WaitOne 无法在主线程执行mre.Set()调用之前完成 - 所以您可以利用并将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()的调用,并且工作者线程永远不会退出。您可能希望在mre.Set()中执行finally

 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();
 }

答案 1 :(得分:0)

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

P.S。我受限于.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;
        }
    }
}