c# - 使用多个相关的可终结对象实现可终结处理模式

标签 c# .net pinvoke idisposable finalizer

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

但是,我正在为 native API 实现一个 C# 包装器,我在其中包装多个相关的非托管资源,并且看起来需要多个类,每个类都实现可终结处理模式。问题是处置模式的指南说可终结的 A 不应该依赖可终结的 B,这正是我需要的:

Dispose Pattern on MSDN :

X DO NOT access any finalizable objects in the finalizer code path, because there is significant risk that they will have already been finalized.

For example, a finalizable object A that has a reference to another finalizable object B cannot reliably use B in A’s finalizer, or vice versa. Finalizers are called in a random order (short of a weak ordering guarantee for critical finalization).

所以这是我的限制条件:

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

native 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 对此并不清楚。

最佳答案

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

如果foo 持有对bar 的引用,并且除了Finalization Queue 之外的任何其他地方都没有对任何一个的引用,系统可能会调用Finalizebar 上调用 foo 上的 Finalize 之前或之后。如果 barfoo 持有彼此的引用,并且双方都同意如何协调清理,那么无论哪一个拥有它的 Finalize 都是可能的 方法首先调用以按照类型语义要求的顺序清理两个对象。但是,没有普遍接受的模式可以做到这一点;任何实现此类事情的人都必须安排自己的协调。

还要注意 WeakReference 有一个相当烦人的怪癖:如果将 true 传递给 WeakReference 的构造函数,那么它的目标有一个已注册的终结器将防止它失效,直到该终结器运行或未注册,但是如果不存在对 WeakReference 本身的强引用,它的终结器将使它无效 < em>即使目标仍然有效。因此,在上面的场景中,如果 bar 持有 fooWeakReference,将涉及三个终结器:foobarbarWeakReference。如果 foo 应该首先被清理,bar 可以做到这一点的唯一方法是持有一个强引用,或者存储一个 WeakReference可以通过静态引用访问的某个地方[这会产生自己的危险]。

关于c# - 使用多个相关的可终结对象实现可终结处理模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20752038/

相关文章:

c# - 使用 WinForms 进行线程化?

c# - 链接的 IEnumerable 方法Where和Select比单独执行它们慢吗?

c# - 更改C#中的音频格式

c# - 将结构列表从 C# 应用程序传递到 C++ DLL

c# - 如何在 ASP.NET Core 3.1 中获取当前的 JsonSerializerOptions?

c# - 遍历定义的具体类型的成员?

c# - C#中标识符的@前缀

c# - Entity Framework - DbMigrator 不执行迁移类

c# - AudioQueueEnqueueBufferWithParameters 是否在 Monotouch 中实现?

c# - 如何在 C# 中将 LPCWSTR 编码为字符串?