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

And*_*ott 17 .net com finalizer

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

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

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

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

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

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

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

And*_*ott 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

但在正常情况下CLR没有自己托管,我被告知这应该有效.


Jar*_*Par 7

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