我非常熟悉非终结类型的Dispose模式,例如,包含我们想要进行确定性清理的某种托管资源的类型。这些类型通常不实现终结器,因为它完全没必要。
但是,我正在为本机API实现一个C#包装器,我将包装多个相关的非托管资源,并且看起来需要多个类,每个类都实现了finalizable-dispose模式。问题在于配置模式的指导方针说,可最终确定的A不应该依赖于可最终化的B,这正是我所需要的:
X不要访问终结器代码路径中的任何可终结对象,因为它们已经完成的风险很大。
例如,具有对另一个可终结对象B的引用的可终结对象A不能在A的终结器中可靠地使用B,反之亦然。终结器以随机顺序调用(缺少关键终结的弱排序保证)。
所以这是我的约束:
本机API看起来像这样:
APIHANDLE GizmoCreateHandle();
CHILDHANDLE GizmoCreateChildHandle( APIHANDLE apiHandle );
GizmoCloseHandle( APIHANDLE apiHandle );
GizmoCloseChildHandle( APIHANDLE apiHandle, CHILDHANDLE childHandle);
这种天真的方法将分为两部分:
所以一切看起来都像这样:
[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对此并不清楚。
答案 0 :(得分:2)
关于最终化要记住的重要事项是垃圾收集器保证不会丢弃与任何对象关联的字段,除非它可以保证该对象不存在曾的引用,但不保证至于它将在一个对象上调用Finalize
的序列或线程上下文,它也无法控制Finalize
方法(或任何其他方法)对对象的作用。
如果foo
包含对bar
的引用,并且除了在终结队列中以外的任何地方都没有引用,则系统可能会在Finalize
之前调用bar
或在Finalize
上调用foo
之后。如果bar
和foo
保持对彼此的引用,并且都同意如何协调清理,则可能有可能首先调用其Finalize
方法进行清理两种对象都以类型语义所需的顺序排列。然而,没有普遍接受的模式;任何实施此类事情的人都必须安排自己的协调。
另请注意WeakReference
有一个相当恼人的怪癖:如果将true
传递给WeakReference
的构造函数,则其目标具有已注册的终结器的事实将阻止它在终结器运行或未注册之前无效,但如果WeakReference
本身不存在强引用,则即使目标仍然有效,其终结器也会使其无效。因此,在上面的场景中,如果bar
持有WeakReference
到foo
,则会涉及三个终结器:foo
,bar
的终结器,以及bar
的{{1}}。如果WeakReference
应该首先得到清理,那么foo
可以实现的唯一方法是通过持有强引用,或者存储bar
某个可以通过静态引用访问的地方[创造自己的危险]。