这是GC.ReRegisterForFinalize()的错误用法吗

时间:2014-08-15 11:21:07

标签: c# performance garbage-collection

所以我听说GC.ReReisterForFinalize()通常应该避免,但我认为没有理由不将它用于我的具体问题。所以首先这是一个对性能非常敏感的代码区域,因此速度非常重要。

我有一堆非托管资源和小型(8字节)托管对象,每个托管对象都包装其中一个非托管资源。创建包装器时,它会在堆栈上推送非托管资源并将其包装起来。当它被破坏而不是从堆栈中间移除它的资源(这将导致整个堆栈移位)时,它可以执行以下两种操作之一:它可以将表示其堆栈位置的int推入队列,因此下一个对象created可以包装该堆栈位置而不是推送新的堆栈位置,或者它可以将自身推入队列并重新注册以进行最终化并重新使用。

差别可能很小,我意识到我可能会分裂头发,但至少在第二种方法中我每次都会保存分配和释放。那么下行呢?

1 个答案:

答案 0 :(得分:2)

好的,让我把它写下来作为答案。

GC.ReRegisterForFinalize有两种用途 - 还原GC.SuppressFinalize()方法,并“复活”已经在其终结器中的对象(~SomeType方法)。

您只需要了解最终确定的效果。基本思想是,拥有一些非托管资源的托管对象将有机会在垃圾收集器收集非托管资源时释放它。因为在大多数情况下,你想要更早地释放资源(例如发布SqlConnection以确保我们没有保持不必要的套接字打开),引入了优化,这是“一次性模式”的一部分

因此,终结器将包含用于摆脱非托管资源的代码,而不包含任何其他内容。为了处理早期处理,我们将实现一个Dispose方法,该方法进行一些清理,包括释放非托管资源。现在,类型确实有一个终结器,因此默认情况下它已注册完成 - 但是,我们之前已经发布了这些非托管资源,所以没有必要在集合上最终确定。因此,您调用GC.SuppressFinalize(this),它基本上从项目列表中删除此实例以最终确定集合。

但是,如果您的类不仅仅是非托管资源的简单包装器,但是例如允许您再次释放并重新获取它,该怎么办? GC.ReRegisterForFinalize拯救了 - 每当您重新获取非托管资源时,您都会再次注册以确保正确清理。

这有点违反了处理.NET中未管理资源的简单原则,基本上托管包装应该尽可能小而简单。因此,真正做一些实际工作的真正的类将使用这个小包装而不是直接使用非托管资源。这就是为什么你真的不需要GC.ReRegisterForFinalize的原因之一,以及为什么在代码中看到它可能看起来很可疑。

然而,GC.ReRegisterForFinalize的第二次使用变得更加令人反感。它允许您停止实例的最终化,并使其恢复生机。听起来不是太糟糕?嗯,首先,它表明您的设计可能存在缺陷。但更重要的是,可能已经收集了仅由此实例引用的所有其他实例(例如,指向另一个.NET对象的字段)。这是引入非常难以重现的错误的好方法。程序员不喜欢那些,所以你大多试图尽可能地避免这些。

所以,TL; DR版本:你可能不需要GC.ReRegisterForFinalize。如果将托管包装器保留在“未使用”对象列表中,则不会收集它,因此也不会最终确定。如果您首先释放非托管资源(在Dispose中包括对GC.SuppressFinalize的调用),然后想要为同一托管实例创建新的非托管资源,则只会产生影响。这可能不是你想要的(实际上没有太多意义)。