假设如下:
IDisposable
。sealed
- 一个类无法派生并添加非托管资源。using
语句中使用 - 即完成后调用Dispose()
。这个类有3种可能的IDisposable
实现:
Dispose
成员上调用Dispose()
的最小IDisposable
方法 - 没有终结器。IDisposable
实现但是缺少GC.SuppressFinalize(this)
中通常的Dispose()
调用。IDisposable
实现(以及在GC.SuppressFinalize(this)
中调用Dispose()
)。以下陈述是否正确?我理解正确吗?
SuppressFinalize
被称为终结者将不会运行。这种情况仍然会导致对象进入终结器队列的开销很小,但终结器实际上并没有运行。这里的关键点是,很有可能认为“我通过调用SuppressFinalize
避免了终结器开销” - 但我认为(并且想澄清)这是不正确的。对象在终结器队列中的开销仍然会产生 - 你要避免的只是实际的终结器调用 - 在通常情况下只是“我已经处理掉了”。
注意:这里的“完全标准IDisposable
实现”我的意思是标准实现,旨在涵盖非托管和托管资源的情况(请注意,这里我们只有托管对象成员)。
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
private bool _disposed;
protected virtual void Dispose(bool disposing) {
if (_disposed)
return;
if (disposing) {
// dispose managed members...
}
_disposed = true;
}
~AXCProcessingInputs() {
Dispose(false);
}
不同版本的.NET GC可能会有不同的做法,但根据我的理解,任何使用Finalize
方法的对象都将被添加到“终结器队列”(如果放弃请求通知的对象列表),并将保留在该队列中只要它存在。取消注册和重新注册以进行最终化的方法(恕我直言,应该受到Object
保护的成员)在对象标题中设置或清除一个标志,该标志控制是否应将对象移动到“可释放队列”(finalize
方法应该对象的列表)如果发现它被放弃,但不会导致在终结器队列中添加或删除该对象。
因此,覆盖Finalize
的每个类型的每个实例都会对它所参与的每个垃圾收集周期施加一个很小但非零的开销,只要它存在。在放弃对象之前调用SuppressFinalize
将阻止它被移动到可释放队列,但是不会消除由于它在整个存在期间处于可终结队列中而产生的开销。
我建议没有面向公众的对象应该实现Finalize
。 Finalize
方法有一些合法的用法,但是覆盖它的类应该避免保留对最终化不需要的任何东西的引用(有点烦人,如果可终结对象持有对弱引用的唯一引用,弱引用可能在终结符之前无效)运行,即使弱引用的目标仍然存在)。
您应该只在需要清理非托管资源的对象上包含终结器。由于您只有托管成员,因此您不需要终结器 - 如果终结器有终结器并且没有为它们调用GC.SuppressFinalize()
,则终结器将自动运行。
终结器的目标是在GC感觉到时清理非托管资源及其托管包装器,而Dispose
模式的目标是在特定时刻清理任何类型的资源。
没有人会想到“我通过调用SuppressFinalize来避免终结器开销” - 相反,他们应该认为“我已经清理了我的非托管资源,没有必要运行终结器”。
关于编号问题:
Dispose()
上调用should be ignored我有时会使用终结器进行调试,以检查我是否在某个地方丢失了一些处理。如果有人有兴趣,我会对我的系统进行快速测试以检查性能影响(Windows 10,.Net 4.7.1,Intel Core i5-8250U)。
添加终结器并对其进行抑制的成本大约为每个对象60 ns,添加它和忘记调用dispose的成本大约为每个对象800 ns。性能影响与调试/发布版本非常一致,并且附加/不附带调试器,可能是因为垃圾收集器在两个版本中都是相同的。
添加终结器并抑制它的性能影响很小,除非你构建了大量的这些对象,通常情况并非如此。甚至微软自己的Task
也使用了终结器(几乎总是被压制),而且这个类的重量非常轻,性能也很高。所以他们显然同意。
然而,让终结者运行可能会变得非常糟糕。考虑到我的测试用例使用了一个没有引用对象的普通类,它已经慢了一个数量级。拥有大量引用的对象应该花费更多,因为所有这些对象都需要保持活跃一代。这也可能导致在垃圾收集的压缩阶段发生大量复制。
测试的源代码:
using System;
using System.Diagnostics;
namespace ConsoleExperiments
{
internal class Program
{
private static void Main(string[] args)
{
GenerateGarbageNondisposable();
GenerateGarbage();
GenerateGarbageWithFinalizers();
GenerateGarbageFinalizing();
var sw = new Stopwatch();
const int garbageCount = 100_000_000;
for (var repeats = 0; repeats < 4; ++repeats)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
sw.Restart();
for (var i = 0; i < garbageCount; ++i)
GenerateGarbageNondisposable();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("Non-disposable: " + sw.ElapsedMilliseconds.ToString());
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
sw.Restart();
for (var i = 0; i < garbageCount; ++i)
GenerateGarbage();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("Without finalizers: " + sw.ElapsedMilliseconds.ToString());
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
sw.Restart();
for (var i = 0; i < garbageCount; ++i)
GenerateGarbageWithFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("Suppressed: " + sw.ElapsedMilliseconds.ToString());
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
sw.Restart();
for (var i = 0; i < garbageCount; ++i)
GenerateGarbageFinalizing();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("Finalizing: " + sw.ElapsedMilliseconds.ToString());
Console.WriteLine();
}
Console.ReadLine();
}
private static void GenerateGarbageNondisposable()
{
var bla = new NondisposableClass();
}
private static void GenerateGarbage()
{
var bla = new UnfinalizedClass();
bla.Dispose();
}
private static void GenerateGarbageWithFinalizers()
{
var bla = new FinalizedClass();
bla.Dispose();
}
private static void GenerateGarbageFinalizing()
{
var bla = new FinalizedClass();
}
private class NondisposableClass
{
private bool disposedValue = false;
}
private class UnfinalizedClass : IDisposable
{
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
private class FinalizedClass : IDisposable
{
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
}
disposedValue = true;
}
}
~FinalizedClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
}