使用多个相关的可终结对象实现可终结的配置模式

ant*_*duh 5 .net c# pinvoke idisposable finalizer

我非常熟悉非终结类型的Dispose模式,例如,包含我们想要进行确定性清理的某种托管资源的类型.这些类型通常不实现终结器,因为它完全没有必要.

但是,我正在为本机API实现一个C#包装器,其中包含多个相关的非托管资源,并且看起来需要多个类,每个类都实现了finalizable-dispose模式.问题是配置模式的指导方针说最终的A不应该依赖于最终的B,这正是我需要的:

在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);
Run Code Online (Sandbox Code Playgroud)

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

  • 对于API句柄,请遵循典型的SafeHandle模式.
  • 对于子句柄,请遵循典型的SafeHandle模式,但稍微修改一下,因为子句柄需要对API句柄的引用才能实现其ReleaseHandle覆盖 - 添加一个方法,为子SafeHandle提供API句柄它已经建成了.

所以一切都看起来像这样:

    [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.
    }
Run Code Online (Sandbox Code Playgroud)

但是,现在我有一个缺陷 - 我的ChildSafeHandle的ReleaseHandle正在引用另一个句柄来完成它的工作.我现在有两个可定型的一次性物品,其中一个的终结器取决于另一个.MSDN明确表示终结器不应该依赖于其他可终结的对象,因为它们可能已经完成,或者如果.Net要支持多线程终结,它们可能会同时进行最终确定.

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

sup*_*cat 2

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

如果foo持有对 的引用bar,并且除最终队列之外的其他任何地方都不存在对其中任何一个的引用,则系统可能会在调用之前或之后Finalize调用。如果和保持彼此的引用,并且两者都同意如何协调清理,则无论哪一个首先调用其方法,都可以按照类型语义所需的顺序清理两个对象。然而,这样做并没有普遍接受的模式。任何实施此类事情的人都必须安排自己的协调。barFinalizefoobarfooFinalize

还要注意,它WeakReference有一个相当烦人的怪癖:如果传递true给 a 的构造函数WeakReference,则其目标具有已注册的终结器这一事实将阻止它失效,直到该终结器运行或取消注册,如果不存在对 a 的强引用即使目标仍然有效WeakReference,它的终结器本身也会使其无效。因此,在上面的场景中,如果持有 a to ,则将涉及三个终结器: that of 、 that of和 that of s 。如果应该首先清理,唯一的方法是通过持有强引用,或存储可通过静态引用访问的某个位置(这会产生其自身的危险)。barWeakReferencefoofoobarbarWeakReferencefoobarWeakReference