我有一个托管对象,它调用COM服务器来分配一些内存。在托管对象消失之前,托管对象必须再次调用COM服务器以释放该内存,以避免内存泄漏。该对象实现IDisposable
以帮助确保正确释放内存的COM调用。
如果Dispose
方法不被调用,我希望对象的终结器释放内存。问题是,最终确定的规则是你不能访问任何引用,因为你不知道在你之前已经GC和/或最终确定了哪些其他对象。这使得唯一可触摸的对象状态为字段(句柄是最常见的)。
但是调用COM服务器涉及通过运行时可调用包装器(RCW)来释放我有一个存储在字段中的cookie的内存。 RCW是否可以安全地从终结器调用(此时是否保证未进行GC或最终确定)?
对于那些不熟悉finalization的人来说,虽然终结器线程在运行时在托管应用程序域的后台运行,但是对于触摸引用的情况理论上可以正常,在appdomain关闭时也会发生终结,并且以任何顺序 - 不仅仅是参考关系顺序。这限制了您可以认为从终结器触摸的安全性。即使引用为非null,对托管对象的任何引用都可能是“坏”(收集的内存)。
更新:我刚试了一下,得到了这个:
myassembly.dll中发生未处理的“System.Runtime.InteropServices.InvalidComObjectException”类型异常
附加信息:无法使用已与其基础RCW分离的COM对象。
答案 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仍然存在。它可能在终结器队列中位于您的对象之前,因此在析构函数在终结器线程上运行时释放。