复杂对象图中 IDisposable 非托管资源的生命周期问题?

发布于 2024-08-30 10:19:01 字数 2499 浏览 9 评论 0原文

这个问题是关于处理非托管资源(COM 互操作)并确保不会出现任何资源泄漏。如果您能就我是否以正确的方式做事提供反馈,我将不胜感激。

背景:


假设我有两个类:

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

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

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

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

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

问题:


由于我的临时 ManagedObject 对象不是一次性的,我显然无法控制他们会存在多久。然而,与此同时,我可能已经Disposed了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!
    }
}

This question is about dealing with unmanaged resources (COM interop) and making sure there won't be any resource leaks. I'd appreciate feedback on whether I seem to do things the right way.

Background:


Let's say I've got two classes:

  • A class LimitedComResource which is a wrapper around a COM object (received via some API). There can only be a limited number of those COM objects, therefore my class implements the IDisposable interface which will be responsible for releasing a COM object when it's no longer needed.

  • Objects of another type ManagedObject are temporarily created to perform some work on a LimitedComResource. They are not IDisposable.

To summarize the above in a diagram, my classes might look like this:

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

(I'll provide example code for these two classes in just a moment.)

Question:


Since my temporary ManagedObject objects are not disposable, I obviously have no control over how long they'll be around. However, in the meantime I might have Disposed the LimitedComObject that a ManagedObject is referring to. How can I make sure that a ManagedObject won't access a LimitedComResource that's no longer there?

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

I've currently implemented this with a mix of weak references and a flag in LimitedResource which signals whether an object has already been disposed. Is there any better way?

Example code (what I've currently got):


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!
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷情 2024-09-06 10:19:01

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

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

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

Nope, this is not okay. A WeakReference only tells you that a managed object got garbage collected. Which has nothing to do with IDisposable. The point of Dispose() is to release unmanaged resources before the garbage collector does it.

In fact, you got a serious problem if the managed object is in gen #1 and the COM wrapper is in gen #0. The WeakReference can't keep the wrapper alive, it will be collected and the COM object disposed before you got a chance to call Dispose() yourself.

Just store a plain reference to the wrapper object in your managed object. You can set it to null after you call Dispose() so that the wrapper can get collected. And throw ObjectDisposedException if the client code tries to use it and the reference is null. Or recreate it, if that makes sense.

墟烟 2024-09-06 10:19:01

当将弱引用的目标强制转换为对象类型时,如果该对象已被释放,它将返回 null。在执行操作之前,只需检查返回的值是否为 null。请参阅文档中的示例。您可能还会找到有关使用弱引用的文章。这是后一篇文章的相关引用:

建立强有力的参考和
再次使用该对象,投射 Target
WeakReference 的属性
对象的类型。如果目标
属性返回 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! 
    } 
}

When you cast the target of the weak reference to the object type, it will return null if the object has been disposed. Simply check to see if the value you get back is null before performing operations with it. See the example in the documentation. You might also find this article on Using Weak References of use. Here's a relevant quote from the latter article:

To establish a strong reference and
use the object again, cast the Target
property of a WeakReference to the
type of the object. If the Target
property returns null, the object was
collected; otherwise, you can continue
to use the object because the
application has regained a strong
reference to it.

Example:

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! 
    } 
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文