简体   繁体   English

C#将代码块与引用IDisposable对象的匿名方法一起使用

[英]C# Using block with an anonymous method referencing the IDisposable object

Consider the following code: 考虑以下代码:

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

Let's say that the spawned worker takes a bit of time to complete. 假设产生的工作人员需要一些时间才能完成。 We will have left the using block a while ago by the time the worker thread finishes and waits on the ManualResetEvent. 当工作线程完成并等待ManualResetEvent时,我们将在不久前离开using块。 I would assume that the mre would have been closed when leaving the using block (given that it would have been disposed) and this would throw an exception at the very least. 我假设mre在离开using块时会被关闭(假设它会被处置),这至少会引发异常。 Is this a safe assumption to make? 这是一个安全的假设吗?

This example may not be the best one with the ManualResetEvent but it is to illustrate the case where we access an IDisposable object inside an anonymous method within a using block and the anonymous method is called after we have exited the using block. 此示例可能不是使用ManualResetEvent的最佳示例,但它说明了以下情况:我们在using块内的匿名方法内访问IDisposable对象,并在退出using块后调用匿名方法。 Is there some mechanism that keeps hold of the disposable object? 是否有某种机制可以固定一次性物品? I don't believe so but would like some confirmation as to why (if there is some sort of voodoo at work) or why not. 我不这么认为,但是想知道为什么(如果有伏都教在工作)或为什么不这样。

Cheers, 干杯,

Yes, this code is wrong - the outcome is not really defined, but it would be quite reasonable for it to throw an exception at the mrEvent.WaitOne() , since mrEvent is the almost-certainly-now-disposed ManualResetEvent . 是的,此代码是错误的-结果尚未真正定义,但是将结果抛出mrEvent.WaitOne()是很合理的,因为mrEvent是几乎肯定已mrEventManualResetEvent Technically there's a chance that the worker thread was all ready to go, and the worker thread did its "some processing..." faster than the primary thread did the "some other processing...", but: I wouldn't rely on it. 从技术上讲,工作线程可能已经准备就绪,并且工作线程执行“某些处理...”的速度比主线程执行“其他处理...”的速度快,但是:我不会依赖在上面。 So in most cases: mrEvent is dead already. 因此,在大多数情况下: mrEvent已经死了。

As for how to avoid this: perhaps this simply isn't a scenario for using . 至于如何避免这个问题:或许这根本就不是一个场景using But it occurs that since the worker thread does a WaitOne , the worker thread's WaitOne cannot complete before the primary thread performs the mre.Set() call - so you could exploit that and move the using to the worker: 但是会发生这样的情况,因为工作线程执行了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();

Note, however, that this raises an interesting question of what happens if the primary thread throws an exception in the "some other processing..." - the call to mre.Set() would never be reached, and the worker thread would never exit. 但是请注意,这引起了一个有趣的问题,即如果主线程在“其他处理...”中引发异常,将会发生什么—永远不会达到对mre.Set()的调用,而工作线程将永远不会出口。 You might want to do the mre.Set() in a finally : 你可能想要做的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();
 }

In response to my comment (rather than proposing the answer to the question), I created a class to close the ManualResetEvent once done with it without the need to track when the last thread has finished using it. 为了回应我的评论(而不是提出问题的答案),我创建了一个类,一旦完成它,便关闭了ManualResetEvent,而无需跟踪最后一个线程何时使用完它。 Thanks to Marc Gravell for the idea to close it once the WaitOne has completed. 感谢Marc Gravell提出的在WaitOne完成后将其关闭的想法。 I am exposing it here should anybody else need it. 我在这里向其他人公开。

PS I'm constrained to .NET 3.5... hence why I am not using the ManualResetEventSlim. PS我只能使用.NET 3.5。因此,为什么不使用ManualResetEventSlim。

Cheers, 干杯,

Sean 肖恩

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM