3

我想要一种方法来打破IDisposable您突然依赖的某些嵌套类现在实现的链条,IDisposable并且您不希望该接口波及复合层。基本上,我通过'SubscribeWeakly()'IObservable<T>对's的订阅很弱,我想在我离开时清理它们,以免泄漏包装器实例,以防万一永远不会触发。这就是动机,但我也将它用于其他事情。Observable

另一篇文章也有类似的问题,答案基本上表明您仍然可以访问终结器中的一次性用品。但是,您无法保证终结器的运行顺序,因此处置可能会出现问题。

因此,我需要一种方法来保证一次性用品保持活动状态,以便我可以调用Dispose()我的终结器。所以我看了看GCHandle,它允许 C++ 通过将它们及其聚合拉入应用程序句柄来保持(并保持活动状态)托管对象,以使它们保持活动状态,直到句柄被释放并且复合生命周期返回到 .NET 内存的控制经理。来自 C++,我认为类似于 的行为std::unique_ptr会很好,所以我想出了类似于AutoDisposer.

public class AutoDisposer
{
    GCHandle _handle;

    public AutoDisposer(IDisposable disposable)
    {
        if (disposable == null) throw new ArgumentNullException();

        _handle = GCHandle.Alloc(disposable);
    }

    ~AutoDisposer()
    {
        try 
        {
            var disposable = _handle.Target as IDisposable;
            if (disposable == null) return;
            try 
            {
                disposable.Dispose();
            }
            finally 
            {
                _handle.Free();
            }
        }
        catch (Exception) { }
    }
}

在需要在资源消失时处理资源的类中,我只需分配一个像_autoDisposables = new AutoDisposer(disposables). 然后,这AutoDisposer将在包含类的同时被垃圾收集器清理掉。但是,我想知道这种技术会出现什么问题。现在我能想到以下几点:

  • 通过终结器对垃圾收集器产生额外开销
  • .NET 的额外开销必须从托管内存中提取项目到应用程序句柄并返回它们
  • 不可单元测试(我似乎无法预测资源何时返回到 .NET 进行内存管理。)

IDisposable因此,如果我需要确定性地调用Dispose()等等,我会谨慎使用它。

有人看到其他问题吗?这种技术甚至有效吗?

4

2 回答 2

3

可以设计类,以便它们可以相互协调它们的终结行为。例如,一个对象可以接受 type 的构造函数参数Action(bool),并指定如果非 null 它将被调用作为Dispose(bool)[可以读取支持字段Interlocked.Exchange(ref theField, null)以确保最多调用一次委托]的第一步]。如果封装文件的类包括这样的特性,并且被封装在封装有额外缓冲的文件的类中,则文件将通知缓冲类它即将关闭,因此缓冲类可以确保所有必要的数据被写入。不幸的是,这种模式在框架中并不常见。

鉴于缺乏这样的模式,封装缓冲文件的类可以确保在被放弃时设法写出其数据的唯一方法是在文件关闭之前保持对文件某处的静态引用(可能使用 的静态实例ConcurrentDictionary(bufferedWrapper, fileObject)),并确保在清理它时,它将破坏该静态引用,将其数据写入文件,然后关闭文件。请注意,仅当包装对象对其包装的对象保持独占控制时才应使用此方法,并且需要特别注意细节。终结器有许多奇怪的极端情况,很难将它们全部正确处理,任何未能正确处理晦涩的极端情况都可能导致 Heisenbugs。

PS继续这个ConcurrentDictionary方法,如果你使用类似事件的东西,你的主要关注点很可能是(1)确保如果一个对象被遗弃,事件不会引用任何“大”的东西;(2) 确保废弃物品的数量ConcurrentDictionary无法无限制地成长。第一个问题可以通过确保没有从事件到任何重要的互连对象“森林”的“强”引用路径来解决;如果一个多余的订阅只包含对总计 100 字节左右的对象的引用,并且如果任何事件发生时它们会被清除,那么即使是一千个废弃的订阅也将代表一个非常小的问题 [只要数量是有限的]。第二个问题可以通过让每个订阅请求轮询字典中的某些项目(在每个请求或摊销的基础上)来处理,以查看它们是否被放弃,如果是则清理它们。如果某些事件被放弃并且永远不会触发,并且如果没有添加该类型的新事件,则这些事件可能会无限期地存在,但它们将是无害的。

于 2013-08-01T17:29:58.070 回答
3

我想你可能误解IDispoable了——对象上的典型模式IDisposable是松散的:

void Dispose() { Dispose(true); }

void Dispose(bool disposing)
{
    if (disposing)
    {
        // Free managed resources
    }
    // always free unmanaged resources
}

~Finalizer() { Dispose (false); }

因为终结器应该始终处理非托管资源,所以如果您等待它运行(这将在未来某个时间内存约束触发垃圾收集,或者手动触发),您不应该泄漏。如果您想确定何时释放这些资源,那么您将不得不公开IDispoable您的类层次结构。

于 2013-08-01T13:00:10.027 回答