Whilst 'investigating' finalisation (read: trying stupid things) I stumbled across some unexpected behaviour (to me at least).
I would have expected the Finalise method to not be called, whereas it gets called twice
class Program
{
static void Main(string[] args)
{
// The MyClass type has a Finalize method defined for it
// Creating a MyClass places a reference to obj on the finalization table.
var myClass = new MyClass();
// Append another 2 references for myClass onto the finalization table.
System.GC.ReRegisterForFinalize(myClass);
System.GC.ReRegisterForFinalize(myClass);
// There are now 3 references to myClass on the finalization table.
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
// Remove the reference to the object.
myClass = null;
// Force the GC to collect the object.
System.GC.Collect(2, System.GCCollectionMode.Forced);
// The first call to obj's Finalize method will be discarded but
// two calls to Finalize are still performed.
System.Console.ReadLine();
}
}
class MyClass
{
~MyClass()
{
System.Console.WriteLine("Finalise() called");
}
}
Could anyone explain whether this behaviour is intentional and if so why?
This above code was compiled in x86 debug mode and running on the CLR v4.
Many thanks
I do not know what is causing the bizarre behaviour. However, since you are in violation of the documented usage of the method, anything can happen. The documentation for ReRegisterForFinalize says:
Requests that the system call the finalizer for the specified object for which SuppressFinalize has previously been called.
You did not previously call SuppressFinalize before you called ReRegisterForFinalize. The documentation does not say what happens in that situation, and in fact, apparently something really weird happens.
Unfortunately, that same documentation page then goes on to show an example in which ReRegisterForFinalize is called on an object for which SuppressFinalize has not been called.
This is a bit of a mess. I'll take it up with the documentation manager.
The moral of the story is, of course, if it hurts when you violate the rules described in the documentation then stop violating them.
I can guess... and this really is only a guess. As Eric says, don't break the rules like this :) This guess is only for the sake of idle speculation and interest.
I suspect that there are two data structures involved:
When the GC notices that an object is eligible for garbage collection, I suspect it checks the object's header and adds the reference to the finalization queue. Your calls to SuppressFinalization
are preventing that behaviour.
Separately, the finalizer thread runs through the finalization queue and calls the finalizer for everything it finds. Your calls to ReRegisterForFinalize
are bypassing the normal way that the reference ends up on the queue, and adding it directly. SuppressFinalization
isn't removing the reference from the queue - it's only stopping the reference from being added to the queue in the normal way.
All of this would explain the behaviour you're seeing (and which I've reproduced). It also explains why when I remove the SuppressFinalization
calls I end up seeing the finalizer called three times - because in this case the "normal" path adds the reference to the finalization queue as well.
Interesting datapoints:
mono 2.6.7 on Win32 doesn't call finalizer
.NET 3.5 on Win32 calls finalizer twice
Test code for reference:
class Program
{
static void Main(string[] args)
{
// The MyClass type has a Finalize method defined for it
// Creating a MyClass places a reference to obj on the finalization table.
var myClass = new MyClass();
// Append another 2 references for myClass onto the finalization table.
System.GC.ReRegisterForFinalize(myClass);
System.GC.ReRegisterForFinalize(myClass);
// There are now 3 references to myClass on the finalization table.
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
// Remove the reference to the object.
myClass = null;
// Force the GC to collect the object.
System.GC.Collect(2, System.GCCollectionMode.Forced);
// The first call to obj's Finalize method will be discarded but
// two calls to Finalize are still performed.
}
}
class MyClass
{
~MyClass()
{
System.Console.WriteLine("Finalise() called");
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With