Finalizer not called after unhandled exception even with CriticalFinalizerObject
Asked Answered
P

2

7

I have test code like this:

public class A : CriticalFinalizerObject 
{
    ~A()
    {
        File.WriteAllText("c:\\1.txt", "1z1z1");
    }
}

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        throw new Exception();
    }
}

First I tried running it without deriving A from CriticalFinalizerObject. Finalizer wasn't called after end of this program. That surprised me as I thought it was more deterministic but okay. Then I've read about CriticalFinalizerObject's that ensure their finalizers will be called. I derived A from it. Guess what. It still doesn't get executed. What am I doing/understanding wrong?

(Please don't write obvious stuff about garbage collector being non-deterministic, I know that. It is not the case as the program is over and I imagined I could safely clean up after a nice unhandled managed exception.)

Pollux answered 18/5, 2012 at 16:9 Comment(0)
F
10

Firstly, let's read about CriticalFinalizerObject in MSDN, we can read, that:

In classes derived from the CriticalFinalizerObject class, the common language runtime (CLR) guarantees that all critical finalization code will be given the opportunity to execute, provided the finalizer follows the rules for a CER, even in situations where the CLR forcibly unloads an application domain or aborts a thread.

The main word here is UNLOAD.

Secondly, let's read MSDN again, this time about Exceptions in managed threads:

If these exceptions are unhandled in the main thread, or in threads that entered the runtime from unmanaged code, they proceed normally, resulting in termination of the application.

The main word is TERMINATION.

So, when there is an unhandled exception in main thread - app terminates, but CriticalFinalizerObject helps only on unloading of Domain.

For example, CriticalFinalizerObject can helps in such situation:

// Create an Application Domain:
AppDomain newDomain = AppDomain.CreateDomain("NewApplicationDomain");

// Load and execute an assembly:
newDomain.ExecuteAssembly(@"YouNetApp.exe");

//Unload of loaded domain
AppDomain.Unload(newDomain);

This is a situation, where domain was unloaded, and CriticalFinalizerObject guarantee you, that your finalizer will be called.

In your situation with terminating of app you can try to subscribe to

AppDomain.CurrentDomain.UnhandledException

and manually finalize your objects.

UPD: Jeffrey Richter in his book "CLR via C#" wrote about CriticalFinalizerObject, that it's for situations where you send your code for example to SQLServer, which can run C# as a procedures. In such case CriticalFinalizerObject helps you to clean your object, if SQLServer will unload your library's Domain. Also CriticalFinalizerObject is for situations where you need in finalizer of object to call method of another object, because of CriticalFinalizerObject guarantee you, that it's finalizer will be called after finalizers of all non CriticalFinalizerObject objects.

Freudian answered 18/5, 2012 at 17:0 Comment(1)
Thanks for the detailed answer! I would say words like "terminated" vs "unloaded" are not very well defined in these MS docs. In common sense, having something "terminated" does not necessarily mean that it will not be "unloaded", though as we see it is not like this here...Pollux
E
0

Ok. Wrote a simple test. If I have an exception in the constructor where I create my A object, indeed could not make finalizer to work, but when I created A object not in constructor of other class but in other method, and then throw exception it worked.

So I suspect, if constructor never finished constructing class, and its creation terminated, object is never created, stack cleared, objects are removed from heap without finalization like they never happen.

It is my guess. But to solve the problem, I'd wrap critical constructing code constructing objects in try-catch-finally and call clean up code explicitely.

Example: This worked

  public Form1()
       {
           InitializeComponent();
       }
       protected override void OnLoad(EventArgs e)
       {
           base.OnLoad(e);
           var a = new A();
           throw new Exception();
       }
   }

This did not

public Form1()
       {
           InitializeComponent();
           var a = new A();
           throw new Exception();
       }

   }
Euglena answered 18/5, 2012 at 16:14 Comment(3)
It doesn't get called - as you see, I create a file in the finalizer - it never appears.Pollux
GC.Collect() where? If before throwing exception then surely it will work. But I cannot write any code after throw...Pollux
To handle unhandled you can still use AppDomain.CurDomain.UnhandledException event.Euglena

© 2022 - 2024 — McMap. All rights reserved.