【问题标题】:Disposing my System.IDisposable object in my finalizer在我的终结器中处理我的 System.IDisposable 对象
【发布时间】:2015-09-29 14:20:46
【问题描述】:

关于如果我的对象管理其他实现 System.IDisposable 的托管对象时该怎么办,StackOverflow 上有几个讨论。

注意:下面我不是谈论非托管代码。我完全理解清理非托管代码的重要性

大多数讨论都说,如果您的对象拥有另一个实现System.IDisposable 的托管对象,那么您还应该实现System.IDisposable,在这种情况下,您应该调用您的对象持有的一次性对象的Dispose()。这是合乎逻辑的,因为您不知道您拥有的一次性对象是否使用非托管代码。您只知道另一个对象的创建者认为,一旦您不再需要该对象,就立即致电Dispose 是明智的。

StackOverflow 上对 Disposable 模式进行了很好的解释,由社区 wiki 编辑:

Proper use of the IDisposable interface

很多时候,在我读到的提到的链接中:

“您不知道销毁两个对象的顺序。完全有可能在您的Dispose() 代码中,您试图摆脱的托管对象不再存在。”

这让我很困惑,因为我认为只要任何对象持有对对象 X 的引用,那么对象 X 就不会也无法最终确定。

或者换句话说:只要我的对象持有对对象 X 的引用,我就可以确定对象 X 没有最终确定。

如果这是真的,那为什么会这样,如果我持有对我的对象的引用直到我完成,我引用的对象已经完成了?

【问题讨论】:

  • 这就是我的理解:如果Y 拥有对X 的唯一引用,但Y 被孤立,那么YX 都将被销毁。由于不保证顺序,所以X可以在Y之前销毁。
  • 通常情况下,它比您想象的要复杂。我参考了 Eric Lippert 博客上的一篇文章,“Where Everything You Know is Wrong”。 ericlippert.com/2015/05/18/…

标签: c# dispose idisposable finalizer


【解决方案1】:

真相介于两者之间:

  • 对象不能被垃圾回收,所以对象不再“存在”的可能性是不正确的
  • 一个对象可以在没有其他不可终结对象对它的引用时被终结。

如果对象 X 引用对象 Y,但两者都是可终结的,那么对象 Y 完全有可能在对象 X 之前终结,甚至它们同时终结。

如果您的假设是正确的,那么您可以创建两个相互引用的对象(并具有终结器),并且它们可能永远被垃圾回收,因为它们永远不会被终结。

【讨论】:

  • 您最后的评论会解释。显然,即使在我敲定的开始,我也不确定我创建的对象是否仍然存在。这意味着我不能在我的最终确定中做很多事情,只能确保我的非托管对象已被处置。显然,在我的 Dispose(false) 中,我不应该使用我的任何对象,即使我自己创建了它们并且没有将它们分享给其他任何人。
  • @HaraldDutch 如果您正在编写终结器代码,除非您有非托管资源,否则这是不必要的,您应该采取“假设无”的立场。
  • @HaraldDutch:这取决于您所说的“使用”是什么意思。您可以假设它们存在,但不能假设它们尚未最终确定。
  • 我的意思是“使用”,可以调用它的任何方法、属性、字段。
  • @HaraldDutch:好吧,如果你调用一个即使在最终确定之后也能工作的方法,那很好。如果您调用的方法依赖于尚未最终确定的对象,那么您可能会遇到问题。
【解决方案2】:

引用Eric Lippert'sWhen everything you know is wrong, part two

误区:在变量中保留对对象的引用可以防止 变量处于活动状态时停止运行终结器;局部变量 至少在控制权离开所在的块之前始终处于活动状态 本地已声明。

{   
     Foo foo = new Foo();
     Blah(foo);  // Last read of foo
     Bar();
     // We require that foo not be finalized before Bar();
     // Since foo is in scope until the end of the block,
     // it will not be finalized until this point, right?
}

C# 规范声明运行时允许广泛的 检测何时永远不会访问包含引用的存储 再次,并停止将该存储视为垃圾收集器的根。 例如,假设我们有一个局部变量foo,并且写了一个引用 在块的顶部进入它。如果抖动知道一个特定的读取是 该变量的最后一次读取,该变量可以合法地从 立即设置 GC 根; 它不必等到控制权离开 变量的范围。 如果该变量包含最后一个引用,则 GC可以检测到对象不可达并将其放入finalizer队列 立即地。使用GC.KeepAlive 来避免这种情况。

为什么抖动有这个宽容度?假设局部变量是 注册到需要将值传递给Blah() 的寄存器中。如果fooBar() 需要使用的寄存器中,保存没有意义 在Bar() 之前的堆栈上永远不会再读的foo 的值是 叫。 (如果抖动生成的代码的实际细节是 你感兴趣,see Raymond Chen’s deeper analysis of this issue。)

额外的乐趣:运行时使用不那么激进的代码生成和 在 调试器,因为有对象是不好的调试体验 即使变量,您正在调试的内容也会突然消失 引用对象在范围内。这意味着如果你有一个错误 如果对象过早完成,您可能无法 在调试器中重现该错误!

请参阅本文的最后一点,了解更可怕的版本 这个问题。

【讨论】:

  • 越来越多的我得出的结论是,如果我的类拥有实现 System.IDisposable 的对象,那么我也应该实现 System.IDisposable 并确保在我的 Dispose 期间而不是在我的 Finalize 期间调用他们的 Dispose .从我的终结器开始,我无法确定我拥有的托管对象尚未最终确定,因此在终结时我只能访问非托管对象,最好通过调用 Dispose(false)
  • @HaraldDutch 是正确的。除非您有未管理的资源,否则您应该避免使用终结器。如果你有IDisposable 类型的成员变量,那么你的类应该实现IDisposable
【解决方案3】:

如果处理得当,您不必担心如何处理已处理的对象。 Dispose 的每个实现如果之前已被释放,则不应执行任何操作。

确实,您无法知道是否有任何子对象已被释放或终结(因为终结的顺序是随机的,请参阅其他帖子),但无论如何您都可以安全地调用它们的 Dispose 方法。

【讨论】:

  • 您写道:“Dispose 的每个实现都不应做任何事情,如果它之前已被处置”。问题不在于我创建的对象是否已被处置,我是否应该再次处置它:当然不是。问题是:我的对象创建了一个对象,没有人知道它。我的对象还没有处理它,因为没有人调用它的处理,我的对象是否应该调用它在完成时创建的对象的处理。我的对象 sill 有一个对它的引用,所以另一个还没有最终确定,对吗?
  • 从其他答案中我了解到,从我的最终确定开始,我不确定是否仍然存在任何托管对象 - 除了我在最终确定中构建的那些。我唯一可能接触的对象是我自己没有处置的不受管理的对象。
【解决方案4】:

在大多数情况下,当在一个对象上调用 Finalize 时,该对象包含对一个或多个 IDisposable 对象的引用,将适用以下一项或多项:

  1. 另一个对象已经被清理了,在这种情况下调用Dispose 充其量是没用的。
  2. 另一个对象已计划尽快完成,但尚未完成,在这种情况下,可能不需要调用 Dispose
  3. 无法在终结器线程上下文中安全地使用其他对象的清理代码,在这种情况下调用 Dispose 可能会造成灾难性的后果。
  4. 其他对象仍被其他地方的代码使用,在这种情况下调用 Dispose 可能会造成灾难性的后果。

在某些情况下,代码对IDisposable 对象有足够的了解,可以知道上述任何一项都不适用,或者尽管有上述情况,它仍应触发清理;然而,让其他对象提供一个方法而不是Dispose 可能会更好地解决这些情况,该方法可以被最终确定的对象调用。

【讨论】:

    【解决方案5】:

    在所有答案之后,我创建了一个小程序来显示 Jodrell 写的内容(谢谢 Jodrell!)

    • 一个对象一旦不被使用就可以被垃圾回收,即使我有它的引用
    • 只有在不调试时才会这样做。

    我编写了一个分配非托管内存和 MemoryStream 的简单类。后者实现了 System.IDisposable。

    根据 StackOverflow 上的每个人的说法,如果我的 Dispose 被调用,我应该实现 System.IDisposable 并释放非托管内存以及 Dispose 托管 memoryStream,但如果我的终结器被调用,我应该只释放非托管内存。

    我写了一些诊断控制台消息

    class ClassA : System.IDisposable
    {
        IntPtr memPtr = Marshal.AllocHGlobal(1024);
        Stream memStream = new MemoryStream(1024);
            
        public ClassA()
        {
            Console.WriteLine("Construct Class A");
        }
        
        ~ClassA()
        {
            Console.WriteLine("Finalize Class A");
            this.Dispose(false);
        }
        
        public void Dispose()
        {
            Console.WriteLine("Dispose()");
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        
        public void Dispose(bool disposing)
        {
            Console.WriteLine("Dispose({0})", disposing.ToString());
            if (!this.IsDisposed)
            {
                if (disposing)
                {
                    Console.WriteLine("Dispose managed objects");
                    memStream.Dispose();
                }
    
                Console.WriteLine("Dispose unmanaged objects");
                Marshal.FreeHGlobal(memPtr);                
            }
        }
    
        public bool IsDisposed { get { return this.memPtr == null; } }
    }
    

    该程序遵循多次描述的 Dispose 模式,a.o.在Proper use of the IDisposable interface的stackoverflow中@

    顺便说一句:为简单起见,我省略了异常处理

    一个简单的控制台程序创建对象,不使用它,但保留对它的引用并强制垃圾收集器收集:

    private static void TestFinalize()
    {
        ClassA a = new ClassA() { X = 4 };
    
        Console.WriteLine("Start Garbage Collector");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Done");
    }
    

    请注意,变量 a 保存对对象的引用,直到过程结束。我忘记了 Dispose,所以我的终结器应该负责处理

    从你的 main 调用这个方法。从调试器运行(发布)构建并通过命令提示符运行它。

    • 如果从调试器运行,则对象会一直保持活动状态,直到过程结束,所以直到垃圾收集器完成收集之后
    • 如果从命令提示符运行,则对象在过程结束之前就已完成,即使我仍然有对该对象的引用。

    所以 Jodrell 是对的:

    • 非托管代码需要 Dispose() 和 Finalize,使用 Dispose(bool)

    • 托管一次性对象需要 Dispose(),最好通过 Dispose(bool)。在 Dispose(bool) 中,仅在处理时调用托管对象的 Dispose()

    • 不要相信调试器:它会使对象在不同的​​时刻完成,而不是没有调试器

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-21
      • 1970-01-01
      相关资源
      最近更新 更多