【问题标题】:Implement finalizable dispose pattern with multiple related finalizable objects使用多个相关的可终结对象实现可终结的处置模式
【发布时间】:2013-12-23 22:21:01
【问题描述】:

我大致熟悉不可终结类型的 Dispose 模式,例如,包装某种托管资源的类型,我们希望对其进行确定性清理。这些类型通常不实现终结器,因为它完全没有必要。

但是,我正在为原生 API 实现 C# 包装器,在该 API 中,我包装了多个相关的非托管资源,并且似乎需要多个类,每个类都实现 finalizable-dispose 模式。问题是 dispose 模式的指导方针说 finalizable A 不应该依赖 finalizable B,这正是我所需要的:

Dispose Pattern on MSDN:

X 不要访问终结器代码路径中的任何可终结对象,因为它们已经被终结的风险很大。

例如,具有对另一个可终结对象 B 的引用的可终结对象 A 不能在 A 的终结器中可靠地使用 B,反之亦然。终结器以随机顺序调用(缺少关键终结的弱排序保证)。

所以这是我的限制:

  • 要做任何事情,我必须创建一个“API”句柄。
  • 要创建“子”句柄,我必须在“创建子”调用中提供 API 句柄。
  • 两种类型的句柄都不能泄露。
  • 如果 API 句柄已关闭,则其所有子句柄都将隐式关闭。
  • 要关闭子句柄,我必须在本机调用中提供子句柄和 API 句柄

本机 API 如下所示:

APIHANDLE GizmoCreateHandle();

CHILDHANDLE GizmoCreateChildHandle( APIHANDLE apiHandle );

GizmoCloseHandle( APIHANDLE apiHandle );

GizmoCloseChildHandle( APIHANDLE apiHandle, CHILDHANDLE childHandle);

对此的天真方法将分为两部分:

  • 对于 API 句柄,请遵循典型的 SafeHandle 模式。
  • 对于子句柄,遵循典型的 SafeHandle 模式,但稍作修改,因为子句柄需要引用 API 句柄才能实现其 ReleaseHandle 覆盖 - 添加一个方法,将 API 句柄提供给构建后的子 SafeHandle。

所以一切都会像这样:

    [DllImport( "gizmo.dll" )]
    private static extern ApiSafeHandle GizmoCreateHandle();

    [DllImport( "gizmo.dll" )]
    private static extern void GizmoCloseHandle( IntPtr apiHandle );

    [DllImport( "gizmo.dll" )]
    private static extern ChildSafeHandle GizmoCreateChildHandle(ApiSafeHandle apiHandle);

    [DllImport( "gizmo.dll" )]
    private static extern void GizmoCloseChildHandle( ApiSafeHandle apiHandle, IntPtr childHandle );

    [DllImport( "gizmo.dll" )]
    private static extern void GizmoChildModify( ChildSafeHandle childHandle, int flag );

    public class ApiSafeHandle : SafeHandle
    {
        public ApiSafeHandle() : base( IntPtr.Zero, true ) { }

        public override bool IsInvalid
        {
            get { return this.handle == IntPtr.Zero; }
        }

        protected override bool ReleaseHandle()
        {
            GizmoCloseHandle( this.handle );
            return true;
        }
    }

    public class ChildSafeHandle : SafeHandle
    {
        private ApiSafeHandle apiHandle;

        public ChildSafeHandle() : base( IntPtr.Zero, true ) { }

        public override bool IsInvalid
        {
            get { return this.handle == IntPtr.Zero; }
        }

        public void SetParent( ApiSafeHandle handle )
        {
            this.apiHandle = handle;
        }

        // This method is part of the finalizer for SafeHandle.
        // It access its own handle plus the API handle, which is also a SafeHandle
        // According to MSDN, this violates the rules for finalizers.
        protected override bool ReleaseHandle()
        {
            if ( this.apiHandle == null )
            {
                // We were used incorrectly - we were allocated, but never given 
                // the means to deallocate ourselves
                return false;
            }
            else if ( this.apiHandle.IsClosed )
            {
                // Our parent was already closed, which means we were implicitly closed.
                return true;
            }
            else
            {
                GizmoCloseChildHandle( apiHandle, this.handle );
                return true;
            }
        }
    }

    public class GizmoApi
    {
        ApiSafeHandle apiHandle;

        public GizmoApi()
        {
            this.apiHandle = GizmoCreateHandle();
        }

        public GizmoChild CreateChild()
        {
            ChildSafeHandle childHandle = GizmoCreateChildHandle( this.apiHandle );

            childHandle.SetParent( this.apiHandle );

            return new GizmoChild( childHandle );
        }
    }

    public class GizmoChild
    {
        private ChildSafeHandle childHandle;

        internal GizmoChild( ChildSafeHandle handle )
        {
            this.childHandle = handle;
        }

        public void SetFlags( int flags )
        {
            GizmoChildModify( this.childHandle, flags );
        }

        // etc.
    }

但是,现在我有一个缺陷 - 我的 ChildSafeHandle 的 ReleaseHandle 正在引用另一个句柄来完成它的工作。我现在有两个可终结的一次性对象,其中一个终结器依赖于另一个。 MSDN 明确指出终结器不应依赖于其他可终结对象,因为它们可能已经被终结,或者如果 .Net 曾经支持多线程终结,它们可能会同时被终结。

这样做的正确方法是什么?只要我先测试有效性,规则是否允许我从 B 的终结器访问可终结对象 A? MSDN 对此并不清楚。

【问题讨论】:

  • 我们喜欢用户发布 sn-p 是有充分理由的。原样是模糊的,但听起来根本没有什么特别的事情发生。在最终确定时,可终结对象是否引用了另一个可终结对象并不重要。当您实现 IDisposable 时,您会关心它,仅此而已。
  • 关键问题是:假设“父”句柄在“子”句柄之前完成。 这会导致孩子的最终确定失败吗?
  • 如果本机父句柄在本机子句柄之前关闭,则所有内容都会被清理,并且.Net-land 中子句的任何最终确定/处置都不应该做任何事情。
  • 评论者和关闭原因都告诉你原因:代码问题应该包括重现问题所需的最短代码量——在问题本身中。不允许链接到包含该项目的场外资源。这就是为什么。

标签: c# .net pinvoke idisposable finalizer


【解决方案1】:

要记住关于终结的重要一点是垃圾收集器保证不会丢弃与任何对象关联的字段,除非它可以保证不会永远存在对该对象的引用,但不能保证至于它将在对象上调用Finalize 的序列或线程上下文,它也无法控制Finalize 方法(或任何其他方法)可能对对象执行的操作。

如果foo 持有对bar 的引用,并且除了在终结队列中的其他任何地方都不存在对任何一个的引用,则系统可以在bar 上调用Finalize 之前或之后调用@ 上的Finalize 987654328@。如果barfoo 持有彼此的引用,并且双方都同意如何协调清理,则可能有可能首先调用其Finalize 方法的任何一个以清理两个对象的顺序类型语义要求。但是,没有普遍接受的模式可以做到这一点。任何实施这种事情的人都必须安排自己的协调。

还要注意WeakReference 有一个相当烦人的怪癖:如果将true 传递给WeakReference 的构造函数,则其目标具有已注册的终结器这一事实将阻止它在终结器运行之前失效或者未注册,但是如果不存在对 WeakReference 本身的强引用,它的终结器将使其无效即使目标仍然有效。因此,在上述场景中,如果bar 持有WeakReferencefoo,则将涉及三个终结器:foo 的终结器、bar 的终结器和bar 的@987654342 终结器@。如果应该首先清理foo,那么bar 可以实现这一目标的唯一方法是持有一个强引用,或者存储一个WeakReference 可以通过静态引用访问的某个地方[这会产生自己的危险] .

【讨论】:

  • 你是理智的灯塔。谢谢你的回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-18
  • 1970-01-01
  • 1970-01-01
  • 2017-05-11
  • 1970-01-01
相关资源
最近更新 更多