繁体   English   中英

是否存在“使用”块不会调用 Dispose 的情况?

[英]Is there a situation in which Dispose won't be called for a 'using' block?

这是我的电话面试问题:是否有一段时间不会对范围由 using 块声明的对象调用 Dispose?

我的回答是否定的 - 即使在 using 块期间发生异常,仍然会调用 Dispose。

面试官不同意并说如果using包含在try - catch块中,那么在您进入 catch 块时不会调用 Dispose。

这与我对结构的理解背道而驰,我无法找到任何支持面试官观点的内容。 他是正确的还是我误解了这个问题?

导致 Dispose 在 using 块中不被调用的四件事:

  1. 在使用块内时,您的机器出现电源故障。
  2. 您的机器在使用块内部时被原子弹熔化。
  3. 无法捕获的异常,StackOverflowExceptionAccessViolationException其他可能的异常
  4. 环境.FailFast
void Main()
{
    try
    {
        using(var d = new MyDisposable())
        {
            throw new Exception("Hello");
        }
    }
    catch
    {
        "Exception caught.".Dump();
    }

}

class MyDisposable : IDisposable
{
    public void Dispose()
    {
        "Disposed".Dump();
    }
}

这产生了:

Disposed
Exception caught

所以我同意你而不是聪明的面试官......

奇怪的是,我今天早上读到了一种情况,即 Dispose 不会在 using 块中被调用。 在 MSDN 上查看此博客 当您不迭代整个集合时,将使用 Dispose 与 IEnumerable 和 yield 关键字。

不幸的是,这并没有处理例外情况,老实说我不确定那个。 我原以为它会完成,但也许值得用一些代码快速检查一下?

关于电源故障、 Environment.FailFast() 、迭代器或using null作弊的其他答案都很有趣。 但我觉得奇怪的是,没有人提到我的想法是,当最常见的情况Dispose()甚至不会在存在被称为using :当内部的表达using抛出异常。

当然,这是合乎逻辑的: using的表达式抛出了一个异常,所以赋值没有发生,我们无法调用Dispose() 但是一次性对象可以已经存在,尽管它可以处于半初始化状态。 即使在这种状态下,它也已经可以容纳一些非托管资源。 这是正确实现一次性模式很重要的另一个原因。

有问题的代码示例:

using (var f = new Foo())
{
    // something
}

…

class Foo : IDisposable
{
    UnmanagedResource m_resource;

    public Foo()
    {
        // obtain m_resource

        throw new Exception();
    }

    public void Dispose()
    {
        // release m_resource
    }
}

在这里,看起来Foo正确释放了m_resource并且我们也正确using了。 但是由于异常,永远不会调用Foo上的Dispose() 在这种情况下的解决方法是使用终结器并在那里释放资源。

using块被编译器转换为它自己的try / finally块,位于现有的try块中。

例如:

try 
{
    using (MemoryStream ms = new MemoryStream())
        throw new Exception();
}
catch (Exception)
{
    throw;
}

变成

.try
{
  IL_0000:  newobj     instance void [mscorlib]System.IO.MemoryStream::.ctor()
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  newobj     instance void [mscorlib]System.Exception::.ctor()
    IL_000b:  throw
  }  // end .try
  finally
  {
    IL_000c:  ldloc.0
    IL_000d:  brfalse.s  IL_0015
    IL_000f:  ldloc.0
    IL_0010:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0015:  endfinally
  }  // end handler
}  // end .try
catch [mscorlib]System.Exception 
{
  IL_0016:  pop
  IL_0017:  rethrow
}  // end handler

编译器不会重新排列东西。 所以它是这样发生的:

  1. 异常被抛出或传播到using块的try部分
  2. 控制离开using块的try部分,进入它的finally部分
  3. 对象finally块中的代码处理
  4. 控制离开 finally 块,异常传播到外部try
  5. 控制离开外部try并进入异常处理程序

重点是,内部finally块总是在外部catch之前运行,因为异常不会传播到finally块完成。

唯一不会发生这种情况的正常情况是在生成器中(对不起,“迭代器”)。 迭代器变成了一个半复杂的状态机,如果在yield return (但在它被处理之前)变得无法访问,则不能保证finally块运行。

using (var d = new SomeDisposable()) {
    Environment.FailFast("no dispose");
}

是的,在某些情况下不会调用 dispose ......你想多了。 情况是 using 块中的变量为null

class foo
{
    public static IDisposable factory()
    {
        return null;
    }
}

using (var disp = foo.factory())
{
    //do some stuff
}

不会抛出异常,但如果在每种情况下都调用了 dispose 就会抛出异常。 不过,你的面试官提到的具体案例是错误的。

面试官说对了一部分。 Dispose可能无法根据具体情况正确清理底层对象。

例如,如果在 using 块中引发异常,WCF 有一些已知问题。 你的面试官可能正在考虑这个。

这是 MSDN 上的一篇关于如何避免WCF使用块出现问题的文章。 这是Microsoft 的官方解决方法,尽管我现在认为该答案和这个答案的组合是最优雅的方法。

暂无
暂无

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

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