2 minute read

I got a question the other day around calling GC.Collect and GC.WaitForPendingFinalizers.

The issue revolved around a problem where the finalizer was blocked and where the application would call GC.Collect() and GC.WaitForPendingFinalizers() which then caused the thread that called GC.WaitForPendingFinalizers() to hang, and the question was

If we interrupt the thread that calls GC.WaitForPendingFinalizers() will this also interrupt the finalizer thread, i.e. unblocking the finalizer?

I would be very surprised if it did, but I can see where the question came from as the MSDN documentation for GC.WaitForPendingFinalizers is a bit ambiguous.

When the garbage collector finds objects that can be reclaimed, it checks each object to determine the object’s finalization requirements. If an object implements a finalizer and has not disabled finalization by calling SuppressFinalize, the object is placed in a list of objects that are marked as ready for finalization. The garbage collector calls the Finalize methods for the objects in this list and removes the entries from the list. This method blocks until all finalizers have run to completion.

The thread on which finalizers are run is unspecified, so there is no guarantee that this method will terminate. However, this thread can be interrupted by another thread while the WaitForPendingFinalizers method is in progress. For example, you can start another thread that waits for a period of time and then interrupts this thread if this thread is still suspended.

Just to make sure, I decided to test this in a small sample asp.net page with 3 buttons

  • Button 1 Creates a bunch of objects called BadFinalizer() that sleeps for 5000 ms in the ~BadFinalizer() method
  • Button 2 Starts a new thread that calls GC.Collect(); GC.WaitForPendingFinalizers();
  • Button 3 Aborts this thread

Attaching windbg I could confirm my suspicion, after clicking on button1 and button2 I had two threads

The blocked finalizer

OS Thread Id: 0x153c (20)
02a0f8fc 7d61cca8 [HelperMethodFrame: 02a0f8fc] System.Threading.Thread.SleepInternal(Int32)
02a0f950 0f7d0740 BadFinalizer.Finalize()
02a0fc1c 79fbcca7 [ContextTransitionFrame: 02a0fc1c]
02a0fcec 79fbcca7 [GCFrame: 02a0fcec]

and the thread that waits in GC.WaitForPendingFinalizers()

OS Thread Id: 0x1bec (1)
0097ef1c 7d61d051 [HelperMethodFrame: 0097ef1c] System.GC.WaitForPendingFinalizers()
0097ef6c 0f7d0707 TestPage.WaitForFinalization()
0097ef70 793b0d1f System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
0097ef78 793740ab System.Threading.ExecutionContext.runTryCode(System.Object)
0097f39c 79e7c74b [HelperMethodFrame_PROTECTOBJ: 0097f39c] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object)
0097f404 79373ff7 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0097f41c 79373ede System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0097f434 793b0c68 System.Threading.ThreadHelper.ThreadStart()
0097f660 79e7c74b [GCFrame: 0097f660]
0097f950 79e7c74b [ContextTransitionFrame: 0097f950]

And after clicking button 3, the thread sitting in GC.WaitForPendingFinalizers() was gone, while the finalizer was still tugging away…

So, the answer is no, if you abort the thread that called WaitForPendingFinalizers() you will just abort that thread, the finalizer will keep going.

Laters, Tess