从终结器调用RCW是否安全?

时间:2009-10-15 17:45:10

标签: .net com finalizer

我有一个托管对象,它调用COM服务器来分配一些内存。在托管对象消失之前,托管对象必须再次调用COM服务器以释放该内存,以避免内存泄漏。该对象实现IDisposable以帮助确保正确释放内存的COM调用。

如果Dispose方法被调用,我希望对象的终结器释放内存。问题是,最终确定的规则是你不能访问任何引用,因为你不知道在你之前已经GC和/或最终确定了哪些其他对象。这使得唯一可触摸的对象状态为字段(句柄是最常见的)。

但是调用COM服务器涉及通过运行时可调用包装器(RCW)来释放我有一个存储在字段中的cookie的内存。 RCW是否可以安全地从终结器调用(此时是否保证未进行GC或最终确定)?

对于那些不熟悉finalization的人来说,虽然终结器线程在运行时在托管应用程序域的后台运行,但是对于触摸引用的情况理论上可以正常,在appdomain关闭时也会发生终结,并且以任何顺序 - 不仅仅是参考关系顺序。这限制了您可以认为从终结器触摸的安全性。即使引用为非null,对托管对象的任何引用都可能是“坏”(收集的内存)。

更新:我刚试了一下,得到了这个:

  

myassembly.dll中发生未处理的“System.Runtime.InteropServices.InvalidComObjectException”类型异常

     

附加信息:无法使用已与其基础RCW分离的COM对象。

2 个答案:

答案 0 :(得分:16)

我从CLR团队自己发现它确实不安全 - 除非你在RCW上分配一个GCHandle,而它仍然是安全的(当你第一次获得RCW时)。这可确保GC和终结器在最终确定需要调用它的托管对象之前没有对RCW进行总计。

class MyManagedObject : IDisposable
{
    private ISomeObject comServer;
    private GCHandle rcwHandle;
    private IServiceProvider serviceProvider;
    private uint cookie;

    public MyManagedObject(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
        this.comServer = this. serviceProvider.GetService(/*some service*/) as ISomeObject;
        this.rcwHandle = GCHandle.Alloc(this.comServer, GCHandleType.Normal);
        this.cookie = comServer.GetCookie();
    }

    ~MyManagedObject()
    {
        this.Dispose(false);
    }

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

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // dispose owned managed objects here.
        }

        if (this.rcwHandle.IsAllocated)
        {
            // calling this RCW is safe because we have a GC handle to it.
            this.comServer.ReleaseCookie(this.cookie);

            // Now release the GC handle on the RCW so it can be freed as well
            this.rcwHandle.Free();
        }
    }
}

事实证明,在我的特定案例中,我的应用程序正在托管CLR本身。因此,它在终结器线程运行之前调用mscoree!CoEEShutdownCOM,这会杀死RCW并导致我看到的InvalidComObjectException错误。

但是在CLR没有托管的正常情况下,我被告知这应该有效。

答案 1 :(得分:7)

从终结器线程访问RCW是不安全的。一旦你到达终结者线程,你无法保证RCW仍然存在。它可能在终结器队列中位于您的对象之前,因此在析构函数在终结器线程上运行时释放。