【问题标题】:Is it safe to call an RCW from a finalizer?从终结器调用 RCW 是否安全?
【发布时间】:2010-12-07 03:14:51
【问题描述】:

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

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

但是调用 COM 服务器需要通过运行时可调用包装器 (RCW) 来释放内存,因为我有一个 cookie 来存储在字段中。 从终结器调用 RCW 是否安全(是否保证此时尚未被 GC 或终结)?

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

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

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

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

【问题讨论】:

  • COM 服务器上的一个方法(为什么有人会在 RCW 本身上调用 Dispose?如果这可能的话,我会感到惊讶)。

标签: .net com finalizer


【解决方案1】:

我从 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 不自行托管的正常情况下,我被告知这应该可以工作。

【讨论】:

  • 你好安德鲁,感谢这篇文章。我正在尝试将您建议的解决方案应用于包装IAudioSessionControl2 COM 接口的类。类实现IDisposable 接口,在IAudioSessionControl2 接口实例上调用Marshal.ReleaseComObject,这会导致您在帖子中提到的错误。为了应用您的建议,我将ISomeObject(我不熟悉)替换为object,但不知道如何使用构造函数的serviceProvider参数,以及调用时应该说明什么服务到GetService?非常感谢。
【解决方案2】:

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

【讨论】:

  • 删除了我的答案,因为在我的情况下,我很幸运使用终结器线程。
猜你喜欢
  • 2021-04-27
  • 2011-11-06
  • 1970-01-01
  • 1970-01-01
  • 2015-12-17
  • 1970-01-01
  • 1970-01-01
  • 2012-07-09
  • 1970-01-01
相关资源
最近更新 更多