As the title states: are there any downsides to recycling resources (like large arrays) in the finalizer of the containing object? So far it works out fine but since finalizers can be a bit funky and difficult to debug I decided to ask here.
The use case is a polygon class which just represents a list of points. My application makes heavy use of large polygons with a few thousand points - allocating those arrays often isn't exactly cheap. A dispose pattern is unfortunately out of the question because they get passed around and pretty much only the GC knows when no other object is referencing them.
This is how I implemented it (arrays always have the length of 2^sizeLog2
and only arrays with sizeLog2 >= MIN_SIZE_LOG_2
get recycled):
Constructor:
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);
}
Create array of size 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];
}
In the increase capacity method this is the line for reregistering the polygon for finalize, if the finalize was suppressed up to this point (capacity can only get increased and only by a factor 2):
if (newSizeLog2 == MIN_SIZE_LOG_2) GC.ReRegisterForFinalize(this);
And this is the destructor:
~Polygon()
{
if (Points != null) Recycle(Points); //enqueues the array in a ConcurrentQueue
Points = null;
}
And yes, I do know that this isn't exactly a fancy programming style, but this is rather performance critical so I really can't just let the GC do all the work because then I end up with hundreds of MB on the large object heap in a few seconds.
Also, the polygons get handled by different threads concurrently.
The short answer is yes there are. Large arrays are not the type of resources finalizers are intended to recycle. Finalizers should be used for external resources and in vanishingly rare instances application state.
I would suggest this article, and/or this one, to better understand some of the pitfalls of finalization.
The crux of the problem with what you've described is this statement: " I really can't just let the GC do all the work because then I end up with hundreds of MB on the large object heap in a few seconds."
But the finalizer is never going to be called until the GC "has done all the work" so you must not have a full understanding what is causing the memory pressure for your application.
It really sounds like you've got in issue with the overall design of your application where you're finding it hard to reason about who owns Polygons and/or Pnts. The result is there are references to polygons/Pnts - somewhere - when you don't expect to have them. Use a good profiler to find where this is happening and fix this overall design problem instead trying to use finalization.