C# 使用 GCHandle-Member 实现 IDisposable
C# implementing IDisposable with GCHandle-Member
鉴于实施 class 包含 GCHandle
-成员,实施 IDisposable
-模式的正确方法是什么?
我想到了这个,但它导致我的应用程序内存泄漏:
public class foo : IDisposable
{
private bool disposed = false;
private readonly byte[] bytes;
private readonly GCHandle hBytes;
private readonly IDisposable someWrapperForUnmanagedData;
public foo(byte[] bytes)
{
this.bytes = bytes;
hBytes = GCHandle.Alloc(bytes, GCHandleType.Pinned);
someWrapperForUnmanagedData = new bar(..., bytes, ...);
}
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing)
{
hBytes.Free();
}
someWrapperForUnmanagedData.Dispose();
disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SupressFinalize(this);
}
~foo() => Dispose(false);
}
Microsoft 文档没有提到 GCHandles 应该如何与 IDisposable
一起使用,而且我似乎无法在网上找到任何内容。
foo
的终结器是否应该调用 hBytes.Free()
?
按照文档,终结器只应该为非托管资源调用清理例程,在这种情况下仅为 someWrapperForUnmanagedData
.
您的 Dispose
方法倒退了。
- 当你的类型的终结器被调用时,你应该释放你拥有的托管资源。
- 当您的类型的
IDisposable.Dispose()
方法被调用时,您应该对您拥有的事物调用 Dispose
,并释放您拥有的托管资源。
原因很简单。令人高兴的情况是您的类型已被处置(有人对其调用 .Dispose()
)。这使您有机会释放您拥有的非托管资源,并且您还需要将此 Dispose()
调用向下传播到您的 children,以便他们也可以释放其非托管资源。
如果有人忘记对你调用 .Dispose()
,那么 GC 可能会通过调用你的 finalize 方法来挽救你。在这种情况下,您应该释放您拥有的任何非托管资源,但您不应该调用任何 children。如果您的任何 children 有自己的终结器,那么:
- GC会单独调用它们的finalizer,所以你不应该也调用它。
- 无法保证类型最终确定的顺序,因此您的 children 可能已经完成。
注意术语所有权的使用。一个非托管资源应该只有一个拥有它的东西,那就是负责释放它的东西。
请注意,您的 someWrapperForUnmanagedData
是 而非 非托管类型。这是一个 C# class,非常易于管理。非托管类型类似于原始指针。如果 someWrapperForUnmanagedData
本身拥有一些其他非托管类型,它应该实现 IDisposable
并定义一个终结器以确保它们被释放。
GCHandle
没有自己的终结器,因此它无法被 GC Free()
编辑。因此,它在这里算作非托管资源:您需要手动对其调用 Free()
。
所以:
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing)
{
someWrapperForUnmanagedData.Dispose();
}
hBytes.Free();
disposed = true;
}
请注意,正如 this doc 明确指出的那样,如果可能,您应该使用 SafeHandle
(或其子 class 之一)。 SafeHandle
实现了自己的终结器,因此您不必这样做。
运行时也知道 SafeHandle
,这让它避免了一些非常讨厌的竞争条件,例如您的类型可以在进行非托管调用时同时完成,从而导致严重的崩溃。如果您不了解这场比赛,这意味着您绝对应该使用 SafeHandle
.
鉴于实施 class 包含 GCHandle
-成员,实施 IDisposable
-模式的正确方法是什么?
我想到了这个,但它导致我的应用程序内存泄漏:
public class foo : IDisposable
{
private bool disposed = false;
private readonly byte[] bytes;
private readonly GCHandle hBytes;
private readonly IDisposable someWrapperForUnmanagedData;
public foo(byte[] bytes)
{
this.bytes = bytes;
hBytes = GCHandle.Alloc(bytes, GCHandleType.Pinned);
someWrapperForUnmanagedData = new bar(..., bytes, ...);
}
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing)
{
hBytes.Free();
}
someWrapperForUnmanagedData.Dispose();
disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SupressFinalize(this);
}
~foo() => Dispose(false);
}
Microsoft 文档没有提到 GCHandles 应该如何与 IDisposable
一起使用,而且我似乎无法在网上找到任何内容。
foo
的终结器是否应该调用 hBytes.Free()
?
按照文档,终结器只应该为非托管资源调用清理例程,在这种情况下仅为 someWrapperForUnmanagedData
.
您的 Dispose
方法倒退了。
- 当你的类型的终结器被调用时,你应该释放你拥有的托管资源。
- 当您的类型的
IDisposable.Dispose()
方法被调用时,您应该对您拥有的事物调用Dispose
,并释放您拥有的托管资源。
原因很简单。令人高兴的情况是您的类型已被处置(有人对其调用 .Dispose()
)。这使您有机会释放您拥有的非托管资源,并且您还需要将此 Dispose()
调用向下传播到您的 children,以便他们也可以释放其非托管资源。
如果有人忘记对你调用 .Dispose()
,那么 GC 可能会通过调用你的 finalize 方法来挽救你。在这种情况下,您应该释放您拥有的任何非托管资源,但您不应该调用任何 children。如果您的任何 children 有自己的终结器,那么:
- GC会单独调用它们的finalizer,所以你不应该也调用它。
- 无法保证类型最终确定的顺序,因此您的 children 可能已经完成。
注意术语所有权的使用。一个非托管资源应该只有一个拥有它的东西,那就是负责释放它的东西。
请注意,您的 someWrapperForUnmanagedData
是 而非 非托管类型。这是一个 C# class,非常易于管理。非托管类型类似于原始指针。如果 someWrapperForUnmanagedData
本身拥有一些其他非托管类型,它应该实现 IDisposable
并定义一个终结器以确保它们被释放。
GCHandle
没有自己的终结器,因此它无法被 GC Free()
编辑。因此,它在这里算作非托管资源:您需要手动对其调用 Free()
。
所以:
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing)
{
someWrapperForUnmanagedData.Dispose();
}
hBytes.Free();
disposed = true;
}
请注意,正如 this doc 明确指出的那样,如果可能,您应该使用 SafeHandle
(或其子 class 之一)。 SafeHandle
实现了自己的终结器,因此您不必这样做。
运行时也知道 SafeHandle
,这让它避免了一些非常讨厌的竞争条件,例如您的类型可以在进行非托管调用时同时完成,从而导致严重的崩溃。如果您不了解这场比赛,这意味着您绝对应该使用 SafeHandle
.