IDisposable 模式。我的终结者如何处理免费的托管资源?
Posted
技术标签:
【中文标题】IDisposable 模式。我的终结者如何处理免费的托管资源?【英文标题】:IDisposable Pattern. How does my finalizer's call of dispose ever free managed resources? 【发布时间】:2019-04-12 16:00:57 【问题描述】:我有一个实现 Disposable 模式的 A 类,以释放非托管资源,例如取消订阅事件。 B 类使用 A 类,但没有将其包装在 using .. 块中,也没有显式调用 A.Dispose(true),因此通过标准 Dispose(false) 调用在 A 的终结器中调用 A.dispose。但是通过将 bool 参数设置为 false,非托管资源将不会被清理,即不会取消订阅已订阅的事件。终结器不应该调用 Dispose(true) 还是 B 类是否应该在某个时候显式调用 A.Dispose(true),例如在它自己的终结器中?
private bool _disposed = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
if (!_disposed)
if (disposing)
_promotionsSQLTableDependency.Stop();
_awardsSQLTableDependency.Stop();
_progressiveGeneratorService.OnProgressiveLevelsUpdate -= _progressiveUpdateHandler;
_disposed = true;
~PromotionHandler()
Dispose(false);
public void Dispose()
Dispose(true);
GC.SuppressFinalize(this);
【问题讨论】:
【参考方案1】:实现 Disposable 模式以释放非托管资源,例如取消订阅事件。
取消订阅事件并不是需要清理的非托管资源。
B 类使用 A 类,但没有将其包装在 using .. 块中,也没有显式调用 A.Dispose(true)
您应该将其视为程序中的错误。实现IDisposable
的全部意义在于该对象需要被明确地清理,并且所有者已经完成了它。
但是接下来通过设置bool参数为false,非托管资源不会被清理,
但这些不是非托管资源,这就是为什么它们没有在 finally 块中清理的原因。
终结器不应该调用 Dispose(true) 还是 B 类是否应该在某个时刻显式调用 A.Dispose(true),例如在它自己的终结器中?
没有。您不应该与终结器中的托管对象进行交互。这样做是不安全的。由于您在终结器中没有要清理的非托管资源,因此您甚至不应该拥有终结器。
【讨论】:
如何取消订阅事件而不是非托管资源? GC 不会取消订阅该事件,因此事件的引发者仍将拥有对订阅对象的引用。我现在也明白我应该在 B 的代码中的某处调用 A.dispose,但是由于 B 在其整个生命周期中都使用 A,那么它应该放在哪里? @EdgarArakelyan 您正在访问另一个托管 GC 对象。非托管资源类似于文件手、手动分配的内存而不是使用 GC、通过与 C/C++ 互操作提供的资源等。“GC 不会取消订阅事件,因此事件的引发者仍将引用订阅的对象。”是的。带有事件的对象是否会在较长时间内比该对象寿命更长,并且该对象是否包含大量昂贵的资源,难以持有?如果没有,那么这不是问题。 @EdgarArakelyan “但是由于 B 在其整个生命周期中都使用 A,所以它应该放在哪里” 如果一个类型持有对IDisposable
对象的引用作为字段,那么该类型本身应该成为 @987654323 @,并在其自己的 dispose 方法中处置其持有的可处置资源。这样的对象不需要终结器。
@EdgarArakelyan 不,它们都是托管对象。它们都不应该在 if 之外访问。
@EdgarArakelyan 如果 C 的生命周期显着更长,而不仅仅是更长,并且如果 A 的维护成本很高,则应该取消订阅,即它是一个非常大的物体。【参考方案2】:
dispose 方法只能使用 disposing 参数来决定是否释放托管资源。必须始终释放非托管资源。
protected virtual void Dispose(bool disposing)
if (!_disposed)
if (disposing)
// Free managed resources
// always free unmanaged resources
_disposed = true;
如果 Dispose 调用通过垃圾收集器(=通过对 Finalizer 的调用)发生并且 disposing 为 false,则您不需要释放托管资源。垃圾收集器还将调用这些托管对象的终结器(可能更早)。
这就是the documentation says:
在第二个重载中,disposing 参数是一个布尔值 指示方法调用是否来自 Dispose 方法(其 值为真)或来自终结器(其值为假)。
方法体由两段代码组成:
释放非托管资源的块。无论 disposing 参数的值如何,此块都会执行。
释放托管资源的条件块。如果 disposing 的值为 true,则执行此块。托管资源 它释放的可以包括:
实现 IDisposable 的托管对象。条件块可用于调用它们的 Dispose 实现。如果您使用过 包装非托管资源的安全句柄,您应该调用 SafeHandle.Dispose(Boolean) 实现在这里。
消耗大量内存或消耗稀缺资源的托管对象。在 Dispose 中显式释放这些对象 方法释放它们的速度比回收它们快 垃圾收集器的不确定性。
如果方法调用来自终结器(也就是说,如果处置是 false),仅执行释放非托管资源的代码。因为 垃圾收集器销毁托管对象的顺序 在终结期间未定义,调用此 Dispose 重载 false 值阻止终结器尝试释放托管 可能已经被回收的资源。
【讨论】:
所以我应该取消订阅我的 if 语句之外的事件基本上就是你在说什么? @EdgarArakelyan 不,因为事件订阅是托管资源。以上是关于IDisposable 模式。我的终结者如何处理免费的托管资源?的主要内容,如果未能解决你的问题,请参考以下文章
c# 中最简单的 IDisposable 模式是啥? [复制]