【问题标题】:What is the difference between the resources disposed in a finalizer to those released in dispose在终结器中处置的资源与在处置中释放的资源有什么区别
【发布时间】:2019-01-22 08:30:56
【问题描述】:

这是这个问题的后续问题:

Finalize/Dispose pattern in C#

所以我明白,如果我正在创建一个使用非托管资源的类,我应该处置它们。链接问题中的答案说终结器处理了非托管资源。但是,Dispose(Boolean) 方法也在处理非托管资源:

protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    } 

那么finalizer的dispose和dispose方法的dispose有什么区别呢?

【问题讨论】:

  • 正如您在接受的答案中看到的那样,终结器 - 如果存在 .- 通常会调用您的 Dispose- 方法,其标志为 false。
  • 垃圾收集器负责收集垃圾并调用终结器。请注意,不能保证完全会收集垃圾(或者如果会,何时会完成)。相反,Dispose 被手动调用(通常在离开using 范围时)。
  • 所以终结器只存在于调用 Dispose 方法以防用户忘记自己调用它?
  • 是的,终结器的存在是为了确保尽可能释放非托管资源。未释放的非托管资源可能会造成严重破坏 - 想象一下文件句柄在应用程序的生命周期内错误地保持打开状态。
  • 是的,只要你遵循 Dispose() 模式,那就是真的。

标签: c# idisposable finalizer


【解决方案1】:

你会使用它的唯一原因(而且它极具争议性)。

  1. 终结器允许在对象被垃圾收集器删除之前清除它。 (也就是说,GC负责调用它,并从内存中清除对象)如果开发者忘记调用对象的Dispose()方法,则可以释放非托管资源,从而避免泄漏.

有很多理由不这样做,也有很多方法会出错。简而言之,您需要这样做或想要这样做的原因很少

【讨论】:

  • “终结器允许在对象被垃圾收集器删除之前清除它。” GC 调用的不是 finalizer 吗?
  • @HimBromBeere GC 负责调用它,并从内存中清除对象
  • 而15 中不这样做的第一个原因是在终结器中释放资源会加剧资源泄漏的情况:它变得不稳定。
  • @DmitryBychenko 确切地说,这是一个选择你自己的冒险,如何可能做一些讨厌的事情
  • 当 GC 正在清除具有 finalize 方法的对象时,它是否会仅运行 finalize 方法(假设该方法还将清除托管资源),或者它是否会释放托管资源本身假设终结器释放仅限非托管资源?
【解决方案2】:

除了给出的答案:垃圾收集器在运行时调用终结器。

所以你不能依赖在终结器中释放非托管资源的时间!因为它是未知的。

另外,终结器在另一个线程上运行,所以当垃圾收集完成时,终结器可能仍在运行!所以必须通过另一个垃圾收集,才能完全摆脱一个对象。

所以,第一次垃圾收集调用 finalezrs,但对象没有被收集(还有对象,该对象持有对它的引用),它将在第二次垃圾收集中被收集。

【讨论】:

  • 当 GC 正在清除具有 finalize 方法的对象时,它是否会仅运行 finalize 方法(假设该方法还将清除托管资源),或者它是否会释放托管资源本身假设终结器释放仅限非托管资源?
  • @YonatanNir 不,您需要编写代码来释放非托管资源。您可以在 Dispose 方法或终结器中执行此操作。我更喜欢Dispose 方法,因为它在调用后立即运行。垃圾收集器调用终结器,时间未知。
  • 这不是我问的。我问 GC 是否会调用终结器并将其从内存中释放,或者 GC 是否会仅调用终结器,因为 GC 会假定终结器也负责托管资源?
  • @YonatanNir GC 只会在第一次垃圾回收时调用终结器。在另一个线程上调用终结器,因此垃圾收集将在不等待终结器调用的情况下完成。 内存将在第二次垃圾回收时释放
  • Turczyn OK 所以最终它会同时做这两个,但不是同时做。只是为了好奇,GC怎么知道finalizer已经完成了,即使它等待了2次?
【解决方案3】:

带有终结器的对象会经历两个 GC 阶段:第一次,终结器运行,第二次,对象被实际收集并释放内存。除了增加 GC 压力和延迟内存释放回池之外,终结器还具有处理字段可能未处于有效状态的对象的功能。此外,在终结器线程上引发异常会立即关闭整个应用程序,而不会提供任何关于刚刚发生的事情的友好信息。

这就是为什么 Dispose 模式实现总是具有对 GC.SuppressFinalize 的调用的原因,这会导致终结器在对象已经被释放并且 GC 可以在第一次运行时直接释放内存的情况下不运行。

一般来说,如果您的应用程序应该在内存不足或线程中止和随后的 AppDomain 卸载等关键异常情况下幸存下来 - SQL Server 或 IIS 等应用程序就是这种情况,那么使用终结器可能会非常复杂和棘手。

长话短说:除非绝对必要,否则不要使用终结器,如果必须(例如使用非托管资源),还有大量研究等待着您。

您可以在以下博客文章中找到有关此主题的更多阅读内容:

Eric Lippert - When everything you know is wrong

Joe Duffy - Never write a finalizer again

【讨论】:

  • 这个答案的基本思路很好,但只是为了澄清你的第一句话:首先,GC线程识别出该对象是可终结的垃圾并将该对象放入终结器队列中,这是一个GC 根。然后,在终结器线程运行后,该对象通常将自己标记为不可终结。然后,稍后的 GC 将对象识别为不可终结垃圾,并且该内存成为 GC 堆中的一个洞,被压缩掉。
  • 您措辞的方式听起来像是 GC 线程运行终结器,但事实并非如此。它只是将对象放入队列中,以供终结器线程稍后处理。
猜你喜欢
  • 1970-01-01
  • 2016-02-15
  • 1970-01-01
  • 2012-01-05
  • 1970-01-01
  • 1970-01-01
  • 2010-11-05
  • 2015-10-20
  • 2014-07-29
相关资源
最近更新 更多