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 模式。我的终结者如何处理免费的托管资源?的主要内容,如果未能解决你的问题,请参考以下文章

为啥是 IDisposable 接口? [复制]

c# 中最简单的 IDisposable 模式是啥? [复制]

如何处理本机模式取消/错误事件(cordova-plugin-purchase)?

C#中的IDisposable模式用法详解

C#中的IDisposable模式用法详解

如何处理IE首页被篡改的问题