【问题标题】:Lifetime issue of IDisposable unmanaged resources in a complex object graph?复杂对象图中 IDisposable 非托管资源的终身问题?
【发布时间】:2010-04-29 21:41:41
【问题描述】:

这个问题是关于处理非托管资源(COM 互操作)并确保不会有任何资源泄漏。对于我是否似乎以正确的方式做事的反馈,我将不胜感激。

背景:


假设我有两个类:

  • 一个类 LimitedComResource,它是一个 COM 对象的包装器(通过某些 API 接收)。这些 COM 对象的数量有限,因此我的类实现了 IDisposable 接口,该接口将负责在不再需要时释放 COM 对象。

  • 临时创建另一种类型的对象ManagedObject 以在LimitedComResource 上执行一些工作。他们不是IDisposable。

用图表总结以上内容,我的类可能如下所示:

            +---------------+           +--------------------+
            | ManagedObject | <>------> | LimitedComResource |
            +---------------+           +--------------------+
                                                  |
                                                  o IDisposable

(稍后我将提供这两个类的示例代码。)

问题:


由于我的临时 ManagedObject 对象不是一次性的,我显然无法控制它们会存在多长时间。然而,与此同时,我可能有Disposed LimitedComObject ManagedObject 所指的LimitedComObject。 如何确保ManagedObject 不会访问不再存在的LimitedComResource?

            +---------------+           +--------------------+
            | managedObject | <>------> |   (dead object)    |
            +---------------+           +--------------------+

我目前使用弱引用和LimitedResource 中的标志来实现此功能,该标志表示对象是否已被释放。 有没有更好的办法?

示例代码(我目前得到的):


LimitedComResource:

class LimitedComResource : IDisposable
{
    private readonly IUnknown comObject;  // <-- set in constructor

    ...

    void Dispose(bool notFromFinalizer)
    {
        if (!this.isDisposed)
        {
            Marshal.FinalReleaseComObject(comObject);
        }
        this.isDisposed = true;
    }

    internal bool isDisposed = false;
}

ManagedObject:

class ManagedObject
{
    private readonly WeakReference limitedComResource;  // <-- set in constructor

    ...

    public void DoSomeWork()
    {
        if (!limitedComResource.IsAlive())
        {
            throw new ObjectDisposedException();
            //        ^^^^^^^^^^^^^^^^^^^^^^^
            //  is there a more suitable exception class?
        }

        var ur = (LimitedComResource)limitedComResource.Target;
        if (ur.isDisposed)
        {
            throw new ObjectDisposedException();
        }

        ...  // <-- do something sensible here!
    }
}

【问题讨论】:

    标签: .net com-interop idisposable weak-references object-lifetime


    【解决方案1】:

    不,这不好。 WeakReference 仅告诉您托管对象已被垃圾收集。这与 IDisposable 无关。 Dispose() 的目的是在垃圾收集器执行此操作之前释放非托管资源。

    事实上,如果托管对象位于第 1 代而 COM 包装器位于第 0 代中,则会出现严重问题。 WeakReference 无法让包装器保持活动状态,它会在您有机会自己调用 Dispose() 之前被收集并处置 COM 对象。

    只需在托管对象中存储对包装对象的普通引用。您可以在调用 Dispose() 后将其设置为 null,以便收集包装器。如果客户端代码尝试使用它并且引用为空,则抛出 ObjectDisposedException。或者重新创建它,如果这有意义的话。

    【讨论】:

    • 与您可能想告诉我的相反,您几乎让我相信我没有做错事。 ;) 我不希望ManagedObject 强烈引用LimitedComResource,因为ManagedObjects 是临时的/短暂的,而LimitedComResource 包装稀缺资源,这不应该被一个短暂的物体“占据”。我分两步检查我的对象是否仍然可用:首先,通过WeakReference.IsAlive,然后通过!LimitedComResource.isDisposed。 -- 更深层次的问题是我能否以某种方式改进我的对象模型。
    • 奇怪。好吧,你会得到短暂的,一微秒是可能的。分配包装器后的下一个new 可以再次收集它。直播时间将完全随机。
    【解决方案2】:

    当你将弱引用的目标转换为对象类型时,如果对象已被释放,它将返回 null。只需检查您返回的值是否为空,然后再对其执行操作。请参阅documentation 中的示例。您还可以在Using Weak References 上找到这篇文章的使用。这是后一篇文章的相关引述:

    建立一个强有力的参考和 再次使用对象,投射目标 弱引用的属性 对象的类型。如果目标 属性返回 null,对象是 集;否则,您可以继续 使用该对象,因为 应用已恢复强劲 参考一下。

    例子:

    class ManagedObject 
    { 
        private readonly WeakReference limitedComResource;  // <-- set in constructor 
    
        ... 
    
        public void DoSomeWork() 
        { 
            var ur = (LimitedComResource)limitedComResource.Target; 
            if (ur == null) 
            { 
                throw new ObjectDisposedException(); 
            } 
    
            ...  // <-- do something sensible here! 
        } 
    }
    

    【讨论】:

    • 感谢您向我展示了一个方便的快捷方式,使拨打IsAlive() 成为多余。但它没有提供指向的LimitedComResource 可能已经被处置(但尚未被垃圾收集)的可能性......对吗?
    • 对——我假设在你的情况下 Dispose 被终结器调用。如果您知道对象可能仍在使用中,为什么还要显式调用 Dispose?
    猜你喜欢
    • 2011-12-20
    • 2012-05-05
    • 1970-01-01
    • 2013-09-29
    • 1970-01-01
    • 2013-02-02
    • 2012-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多