While tracking down a problem and reading some stuff on garbage collection,I came across the GC.KeepAlive method. Looking at the example in the documentation really got me: In DoWork() the GC sometimes collects the Example object before the Hash property is finished executing. For me this looks like a bug in .NET.
Playing around with the example I found some interesting details: as soon as the Example object has an additional struct or class member variable, it didn’t happen anymore – probably because the Example object now takes longer to be constructed/destroyed? Or does the GC first check all members of the Example object, and if they are no longer used it can collect the Example object? My guess is that I just have luck that it doesn’t happen anymore.
And I noticed another interesting thing: if I run the original example and use Process Explorer to change the affinity so that it runs only on one core, it happens much more frequently.
The problem with the Example class is that the Finalizer method changes its member variables. Although the Hash property returns a cloned byte array this isn’t sufficient because GC kicks in before the clone method is done (at that point the Example object isn’t used any more from the compiler’s point of view).
A solution to get DoWork() to behave correctly (instead of calling KeepAlive) is to implement IDisposable, moving the code from the Finalizer method to Dispose(bool) and putting a using block around the desired scope in DoWork. This causes the Dispose method to be called when exiting the using block; GC can’t kick in earlier than that.
Chris Brumme’s (old) blog entry has some interesting insights on GC.