Whenever an exception is thrown within .NET, execution is transferred to the next location up the call stack that can "Catch" the specific type of exception that was thrown. If the entire call stack is walked without finding any suitable handler it is called an Unhandled Exception. When the .NET runtime receives an unhandled exception, it will raise an event and then force the application domain where the exception was thrown to exit.
There are two events that your application can work with to deal with unhandled exceptions:
This event is invoked when an exception unwinds all the way up the call stack without being caught. As of .NET 2.0, this event signals that the application domain is about to terminate. This does not just terminate the thread. To allow safe termination of threads, the top-level thread entry needs to have its own master try/catch block. However, if general events are caught and "handled", the ThreadAbortException still needs to allow the thread to exit properly when the app domain is unloaded or the process is exiting (that exception may be caught by the thread management layer to end the thread and prevent it from becoming an unhandled exception).
AppDomain.CurrentDomain.UnhandledException can be subscribed to as a normal event. The UnhandledExceptionEventArgs has an IsTerminating property to report whether the application will be terminated (as usual) or is not exiting because the application is running in a "compatibility mode" which follows older .NET behavior and not always terminate.
The primary event to work with in WinForms applications is the Application.ThreadException Event.
This event is provided as part of Windows Forms when running in the message loop (Application.Run()). Each execution of a message is wrapped in a try/catch at the message loop, and exceptions which are caught by it can optionally invoke a registered exception handler (or a default Windows handler if none is registered). This behavior can be configured by the Application.SetUnhandledExceptionMode() call which can set the mode to either CatchExceptions (at the message loop) and send them to Application.ThreadException, or to ThrowExceptions where they become unhandled exceptions.
The call to Application.SetUnhandledExceptionMode() must be made before any Forms are created on the thread (or else it throws an exception) and is also thread-specific.
Registering a handler with Application.ThreadException must be done on a per-thread basis, and can have only a single subscriber (it overwrites any previous subscriber). This subscription is also lost when the internal Application.ThreadContext is disposed upon exit from the message loop (when Application.Run() returns). This disposal can be reported through the Application.ThreadExit event handler (which is not thread-specific and allows multiple subscribers, like a normal event).
The Loupe Agent normally listens for exceptions automatically via the UnhandledException event and Application.ThreadException. However, it is advantageous to not let exceptions on background threads result in an UnhandledException event because it will cause the appdomain to exit. Instead, the top-level Try/Catch block should report the exception through one of two specialized methods:
These methods were designed for use in top-level exception handler where there was no additional context to be gained by additional content or attribution to the location where it was caught.