C# - 在终结器中回收资源的缺点
C# - downsides to recycling resources in finalizer
如标题所述:在包含 object 的终结器中回收资源(如大型数组)是否有任何缺点?到目前为止一切正常,但由于终结器可能有点古怪且难以调试,我决定在这里提问。
用例是一个多边形 class,它仅表示一个点列表。我的应用程序大量使用具有几千个点的大型多边形 - 分配这些数组通常并不便宜。不幸的是,处置模式是不可能的,因为它们会被传递,几乎只有 GC 知道何时没有其他 object 引用它们。
这就是我实现它的方式(数组的长度始终为 2^sizeLog2
,并且只有 sizeLog2 >= MIN_SIZE_LOG_2
的数组才会被回收):
构造函数:
public Polygon(int capacity = 1)
{
//fast method for getting the ceiling of the logarithm of an integer
int sizeLog2 = UIM.CeilingLog2((uint)capacity);
//supress finalize when the array should not be recycled
if(sizeLog2 < TWO_POW_MIN_SIZE) GC.SuppressFinalize(this);
Points = Create(sizeLog2);
}
创建大小为 2^sizeLog2 的数组:
private static Pnt[] Create(int sizeLog2)
{
if (sizeLog2 >= TWO_POW_MIN_SIZE)
{
if (/*try to get an item from recycle queue*/) return result;
Pnt[] points = new Pnt[1 << sizeLog2];
//keep array alive so that it won't get collected by GC when the polygon is
GC.KeepAlive(points);
return points;
}
return new Pnt[1 << sizeLog2];
}
在增加容量的方法中,这是为最终确定重新注册多边形的线,如果最终确定被抑制到这一点(容量只能增加并且只能增加 2 倍):
if (newSizeLog2 == MIN_SIZE_LOG_2) GC.ReRegisterForFinalize(this);
这是析构函数:
~Polygon()
{
if (Points != null) Recycle(Points); //enqueues the array in a ConcurrentQueue
Points = null;
}
是的,我确实知道这并不是一种奇特的编程风格,但这对性能非常关键,所以我真的不能只让 GC 完成所有工作,因为这样我最终会得到数百个MB 在几秒钟内进入大型 object 堆。
此外,多边形由不同的线程同时处理。
简短的回答是有。大型数组不是终结器旨在回收的资源类型。终结器应该用于外部资源和极少数情况下的应用程序状态。
我建议 this article, and/or this one,以更好地理解定稿的一些陷阱。
你所描述的问题的症结在于这个陈述:“我真的不能让 GC 完成所有工作,因为那样我最终会在大对象堆上有数百 MB几秒钟。”
但是在 GC“完成所有工作”之前永远不会调用终结器,因此您一定没有完全了解导致应用程序内存压力的原因。
听起来您确实对应用程序的整体设计有疑问,您发现很难推断出谁拥有多边形 and/or Pnt。结果是有对 polygons/Pnts 的引用 - 某处 - 当您不希望有它们时。使用一个好的分析器来找到发生这种情况的地方并解决这个整体设计问题,而不是尝试使用最终确定。
如标题所述:在包含 object 的终结器中回收资源(如大型数组)是否有任何缺点?到目前为止一切正常,但由于终结器可能有点古怪且难以调试,我决定在这里提问。
用例是一个多边形 class,它仅表示一个点列表。我的应用程序大量使用具有几千个点的大型多边形 - 分配这些数组通常并不便宜。不幸的是,处置模式是不可能的,因为它们会被传递,几乎只有 GC 知道何时没有其他 object 引用它们。
这就是我实现它的方式(数组的长度始终为 2^sizeLog2
,并且只有 sizeLog2 >= MIN_SIZE_LOG_2
的数组才会被回收):
构造函数:
public Polygon(int capacity = 1)
{
//fast method for getting the ceiling of the logarithm of an integer
int sizeLog2 = UIM.CeilingLog2((uint)capacity);
//supress finalize when the array should not be recycled
if(sizeLog2 < TWO_POW_MIN_SIZE) GC.SuppressFinalize(this);
Points = Create(sizeLog2);
}
创建大小为 2^sizeLog2 的数组:
private static Pnt[] Create(int sizeLog2)
{
if (sizeLog2 >= TWO_POW_MIN_SIZE)
{
if (/*try to get an item from recycle queue*/) return result;
Pnt[] points = new Pnt[1 << sizeLog2];
//keep array alive so that it won't get collected by GC when the polygon is
GC.KeepAlive(points);
return points;
}
return new Pnt[1 << sizeLog2];
}
在增加容量的方法中,这是为最终确定重新注册多边形的线,如果最终确定被抑制到这一点(容量只能增加并且只能增加 2 倍):
if (newSizeLog2 == MIN_SIZE_LOG_2) GC.ReRegisterForFinalize(this);
这是析构函数:
~Polygon()
{
if (Points != null) Recycle(Points); //enqueues the array in a ConcurrentQueue
Points = null;
}
是的,我确实知道这并不是一种奇特的编程风格,但这对性能非常关键,所以我真的不能只让 GC 完成所有工作,因为这样我最终会得到数百个MB 在几秒钟内进入大型 object 堆。
此外,多边形由不同的线程同时处理。
简短的回答是有。大型数组不是终结器旨在回收的资源类型。终结器应该用于外部资源和极少数情况下的应用程序状态。
我建议 this article, and/or this one,以更好地理解定稿的一些陷阱。
你所描述的问题的症结在于这个陈述:“我真的不能让 GC 完成所有工作,因为那样我最终会在大对象堆上有数百 MB几秒钟。”
但是在 GC“完成所有工作”之前永远不会调用终结器,因此您一定没有完全了解导致应用程序内存压力的原因。
听起来您确实对应用程序的整体设计有疑问,您发现很难推断出谁拥有多边形 and/or Pnt。结果是有对 polygons/Pnts 的引用 - 某处 - 当您不希望有它们时。使用一个好的分析器来找到发生这种情况的地方并解决这个整体设计问题,而不是尝试使用最终确定。