【问题标题】:IDisposable Pattern. How does my finalizer's call of dispose ever free managed resources?IDisposable 模式。我的终结者如何处理免费的托管资源?
【发布时间】: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);
    }

【问题讨论】:

    标签: c# .net garbage-collection finalizer


    【解决方案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 不,因为事件订阅是托管资源。
    猜你喜欢
    • 1970-01-01
    • 2011-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-06
    • 1970-01-01
    • 2013-09-29
    • 1970-01-01
    相关资源
    最近更新 更多