C# Dispose() - 澄清说明

3
当我调用 object.Dispose() 时,CLR 是否会立即将对象从内存中销毁或在下一个周期中标记该对象以进行删除?
在 Dispose() 后立即调用 GC.SuppressFinalize(),这是否意味着“不要再收集对象以进行处理,因为它已经提交进行处理”?
实际上是哪一代负责销毁?我猜测是第二代。
4个回答

11

首先,IDisposable.Dispose 和 GC(垃圾回收)不是同一个东西。

GC 用于清理内存使用,IDisposable.Dispose 用于有决定性地释放资源,如文件句柄、数据库连接、网络连接等。

让我们先来了解 finalization(终结)。

如果一个对象声明了终结器,当 GC 来释放它的内存时,该对象会被特殊对待。该对象不会立即被释放,而是被放在一个单独的列表中。

后台会运行一个终结线程,遍历该列表并调用这些对象的终结器方法。一旦终结器方法被调用,该对象就会从列表中移除。

这里的关键是,当对象在这个列表中时,它不适合进行回收。这意味着一个具有终结器的对象,一旦它变得适合回收,就会暂时转换成一种状态,即不再适合进行回收,而是待终结。一旦对象再次被发现,在终结器运行并从列表中移除后,它就会被释放。

GC.SuppressFinalize 只是一个对象告诉系统“终结器不再需要运行了,如果你发现这个对象可以回收了,就立即释放它”的一种方式。

另一方面,一旦一个对象实现了 IDisposable.Dispose,它与垃圾收集器并不完全相关。在 GC 系统中没有任何内置的机制会确保调用 Dispose 方法。这意味着一个具有 Dispose 方法的对象可以很容易地在没有调用 Dispose 的情况下被释放。

此外,调用 Dispose 不会以任何方式使该对象适合进行回收。使对象适合进行回收的唯一方法是删除对它的所有强引用,通常是通过让局部变量超出作用域(方法返回)或从其他对象(如列表、事件处理程序等)中删除对对象的引用。

“链接”("link"),如果你愿意这样称呼它,它是一个对象,如果该对象内部包含未受管理的资源,则通常会实现一个终结器。如果资源是托管的,例如 FileStream 对象,则该对象将自行处理必要的终结,否则您应该实现终结器,以便释放未受管理的资源,例如通过 P/Invoke 获取的文件句柄。

通常,该对象的终结器和 IDisposable.Dispose 方法都将清除该资源。然后,通常情况下,Dispose 方法会调用 GC.SuppressFinalize 方法来告知垃圾回收器:"我已经处理过了,如果该对象符合收集要求,则可以直接释放它"。

但是,如果你只调用了 Dispose 方法,但仍然保留了对该对象的引用(例如事件处理程序、静态字段、局部变量等),那么该对象尚未符合收集要求,将不会被释放。

因此,总结一下:

  1. 通常使用 Dispose 方法来释放资源(未受管理或托管),它不会以任何方式影响垃圾回收器是否可以收集该对象,也不会影响何时进行回收。
  2. 使对象符合收集要求的唯一方法是显式地失去对它的所有强引用。
  3. 终结器由垃圾回收器调用,通常用于释放未受管理的资源。
  4. 符合收集要求但具有终结器的对象将被临时放置在列表中(因此不再符合收集条件),直到执行完终结器。

奖励问题:

你认为如果发生以下情况会怎样:

  1. 终结器挂起了?(通常情况下(据我所知),只有一个终结器线程,如果它挂起了,那么列表中的其他对象会发生什么?)

答:如果终结器线程挂起,那么列表中的其他对象也会被阻塞,即这些对象暂时无法被垃圾回收器回收。如果要解决该问题,可以使用 SafeHandle 类或使用自定义池来管理未受管理的资源。
  • 一个对象通过其终结器(通常是将自身插入到静态字段中)使自己不符合作为垃圾回收的一部分。终结器已经运行,并且有一个标记,因此当对象再次可用于收集时,终结器不会自动重新运行。但是,在GC对象中还有其他方法可以重新注册它以进行终结。

  • 2
    为什么一个“答案”会有奖励问题? - SLaks
    2
    因为我希望OP思考问题,而不是盲目接受我所写的作为完整答案 :) - Lasse V. Karlsen

    4

    Dispose是一个普通的CLR方法,通常会调用GC.SuppressFinalize。垃圾回收器与此无关,调用Dispose对于GC没有特殊意义。

    如果Dispose调用GC.SuppressFinalize(this),当GC收集对象时将不会运行终结器。
    但这并不意味着对象会更快地被回收。


    3
    不,Dispose 不会导致垃圾收集器回收对象。你在许多 Dispose 方法中看到 GC.SuppressFinalize() 的原因是许多可处理类实现了终结器以确保处理。我来解释一下。
    如果我实现了一个持有关键资源的类,并且我希望确保(几乎可以肯定)我的类被正确处理,那么我可能不仅依赖于我的类的使用者调用 Dispose 或使用 using。相反,我可能会实现一个终结器,在大多数情况下,当 GC 收集我的对象时会调用它。在终结器中,我会强制调用 Dispose
    但是,具有终结器的对象对于垃圾收集器来说更加复杂。它会在内存中停留更长时间,占用更多内存,并在收集时需要更长时间处理。
    因此,如果用户确实记得正确处理,则我们可以告诉 GC 它不需要为此对象调用终结器。

    1

    对象的代数指的是它经历了多少次垃圾回收(2 为最高)。当一个对象被创建时,它处于第 0 代,只有在垃圾回收时该对象是可达或“活”的(具有强引用),才会晋升到下一代。

    这不包括具有复杂终结器的第 0 代对象,因为终结器在单独的线程上运行,并且完成后以通常的方式进行回收。它们不会晋升。

    对第 2 代对象进行回收可能需要很长时间,因为 GC 可能只需要回收第 0 代对象以释放堆上足够的空间。要强制进行完整的回收,请调用 GC.Collect()。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接