【问题标题】:dispose a list of IDisposables in the finalizer在终结器中处理 IDisposables 列表
【发布时间】:2012-08-27 13:05:22
【问题描述】:

我有几个用于与 c++ dll 通信的非托管内存结构。 每个这样的结构都必须手动释放,所以我将它包装在实现 IDisposableMyUnmanagedStructure 中。

我总是需要可变数量的这些结构,所以我有一个集合MyUnmanagedStructureCollection,它也实现了 IDisposable。

(参见下面的最小示例代码)

只要我的库的用户总是调用 Dispose() 或使用 using() {} 包装集合就没有问题,但我不能保证。即使用户不手动处理集合,我也不想泄漏内存。

当垃圾收集通过终结器调用MyUnmanagedStructureCollection.Dispose() 方法时,据我所知,我不能确定我的private List<MyUnmanagedStructure> 还没有被垃圾收集,那么我该如何处理每个结构那案子?

在我的最终代码中,我是否应该尝试遍历列表,希望它还没有被垃圾回收?

在 try/catch 块中执行此操作并捕获 ObjectDisposedException 是否是一种好习惯?

或者我应该让每个非托管结构“自生自灭”,依靠各个终结器,而在我的集合的终结器中什么都不做?

public class MyUnmanagedStructureCollection : IDisposable
{
    private List<MyUnmanagedStructure> structures;
    private bool disposed = false;

    #region standard IDIsposable pattern
    public ~MyUnmanagedStructureCollection()
    {
        this.Dispose(false);
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            // Dispose unmanaged resources 
            // Should not access managed resources, 
            // the garbage collection may have claimed them already!

            // PROBLEM!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            // this.structures is a List<MyUnmanagedStructure>; so is a managed resource!!!

            foreach (var structure in this.structures)
                 structure.Dispose(disposing)
            this.removeAllMemoryPressure();

            if (disposing) 
            {
                // Dispose managed resources.
                this.structures.Clear();
                this.structures = null;
            }

        }
        disposed = true;
    }
}


public class MyUnmanagedBuffer : IDisposable
{
...
}

【问题讨论】:

  • 如果列表仍然从您的对象中引用,如何收集?只是集合中的对象可能已经完成(但尚未收集)。
  • 好吧,我的对象和对象里面的List会被一起收集起来,据我了解,GC不保证会按照什么顺序销毁它们。不过我可能错了
  • GC不保证finalization的顺序。它保证尚未收集任何可从终结器访问的对象。
  • 我的担忧源于我读到的关于 IDisposable 模式的常见建议,例如here。我读到的所有内容都声称我不应该从终结器访问托管对象。从我的终结器访问托管 List 真的省钱吗?
  • 集合的终结器只会增加不必要的开销。元素的终结者已经完成了工作。最终确定已经确定的元素的额外风险。只需删除集合终结器,它没有任何帮助。

标签: c# garbage-collection unmanaged idisposable finalizer


【解决方案1】:

GC 的工作方式是:

  1. 查找所有可到达的对象
  2. 使用终结器将所有无法访问的对象排入终结队列
  3. 将所有可从终结队列中访问的对象标记为可访问
  4. 释放剩余的无法访问的对象

从正在运行终结器的对象引用的对象还不能被垃圾回收。

您唯一需要注意的是最终确定的顺序是未定义的。因此,列表中的元素可能尚未最终确定,但尚未收集。终结保证是单线程的,所以你也需要加锁。

人们通常会尽量避免这种终结链,因为独立终结更简单。但是,如果某些对象需要先于其他对象进行处理,那么这样的构造是不可避免的。

您还应该考虑使用SafeHandles 进行关键的最终确定。


可达性

终结的准则之一是终结方法不应触及其他对象。人们有时会错误地认为这是因为那些其他对象已被收集。然而,正如我已经解释过的,来自可终结对象的整个可达图都被提升了。

该指南的真正原因是避免接触可能已经完成的对象。那是因为最终化是无序的。

Chris Brumme on finalization

【讨论】:

  • 我的担忧源于我读到的关于 IDisposable 模式的常见建议,例如 here。我读到的所有内容都声称我不应该从终结器访问托管对象。从我的终结器访问托管 List 真的省钱吗?
  • 非常感谢,您的更新很好地解决了这个问题。关于您对 SafeHandle 的建议,我阅读了有关它们的信息,但到目前为止我无法弄清楚如何使用它们,因为创建此非托管对象的代码位于 C-Dll 内部,并且我只在 C# 端获得了一个指针(并且我必须用这个指针调用 C-Dll 中的一个函数才能在最后释放它)
  • 次要技术细节:无论出于何种原因,已注册终结器但标记为立即终结的对象列表的名称是“终结队列”。已发现无法访问的对象列表是“freachable”队列。 (来自msdn.microsoft.com/en-us/magazine/bb985010.aspx 的信息)。名称似乎落后,但据我所知,该网站是正确的。
【解决方案2】:

由于您的 MyUnmanagedBuffer 类是从您的 MyUnmanagedStructureCollection 类的角度来看的托管资源,我认为它应该这样处理它。这意味着MyUnmanagedStructureCollection.Dispose(bool) 方法如下。

protected virtual void Dispose(bool disposing) {
    if (!disposed) {
        // Dispose unmanaged resources 
        // Should not access managed resources, 
        // the garbage collection may have claimed them already!

        if (disposing) {
            // Dispose managed resources.
            // This means that we try to dispose all items in the structures collection.
            if (this.structures != null) {
                foreach (var structure in this.structures) {
                    structure.Dispose(disposing);
                    this.removeAllMemoryPressure(); // What does this?
                }
                this.structures.Clear();
                this.structures = null;
            }
        }
    }

    disposed = true;
}

【讨论】:

  • removeAllMemoryPressure() 调用GC.RemoveMemoryPressure(this.memoryPressure),我不确定是否应该在示例中提及。每当我添加 UnmanagedStructure 时,我都会调用 GC.AddMemoryPressure(SizeOfStructure) 以使 GC 考虑该大小。所以我需要在最后删除完全相同数量的 memoryPressure,无论我的班级如何处理
猜你喜欢
  • 2015-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-26
  • 1970-01-01
相关资源
最近更新 更多