views:

45

answers:

1

I have experienced an odd behavior of the BackgroundWorker or the .NET framework when rethrowing an Exception in the RunWorkerCompleted event.

Exceptions occuring in the background thread are passed to RunWorkerCompleted. There I do a object temp = e.Result to rethrow the exception. Since this is supposed to happen in the main UI thread, I expect the exception to be propagated up to Application.Run(...), which I surrounded with a try-catch block.

When running the application in Visual Studio, this works fine. However, when I execute the exe file outside Visual Studio, the Exception is not handled and makes the application crash.

Could it be that the RunWorkerCompleted event is not executed in the main UI thread?

These posts are not duplicates but rather confirm that my situation is weird:

This is an extremly simplified code example that reproduces the bug: Create a WinForms project and add:

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    try
    {
        Form form = new Form();

        form.Load += delegate
        {
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += (sender, e) => 
            {
                // do nothing
            };
            bw.RunWorkerCompleted += (sender, e) =>
            {
                throw new Exception("Booo!");
            };
            bw.RunWorkerAsync();
        };

        Application.Run(form);
    }
    catch (Exception ex)
    {
        while (ex is TargetInvocationException && ex.InnerException != null)
            ex = ex.InnerException;

        MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

Then try:

  1. Run it in Visual Studio. You'll get a friendly message box before the application quits.
  2. Run the compiled executable outside Visual Studio. You'll get a .NET framework "unhandled exception" message.

The interesting part is that if I hit "continue" in 2, the application persists, with the Form being displayed and taking user input. This must mean that instead of the main UI thread, a background thread crashed. The main thread remains alive.

(Why do I want to do all these things? I display a splash screen, and while some application startup work is done in background, the splash screen displays the progress by means of a label and a progress bar. So I have to create and display the splash screen and then grab the main UI thread to start the BackgroundWorker. In the original code it reports progress back to the main thread which updates the splash form. If during startup an exception occurs, I want to catch it. In certain cases they are "business exceptions" which have a specific meaning, e.g. "you are not authorized to use this application". In these cases I display a friendly message box before I let the application die. Further I have a finally block to clean up resources. I'm not sure whether the .NET framework "unhandled exception" dialog executes the finally block before killing the app.)

+2  A: 

There is a try/catch block around the message loop code inside Application.Run() that catches unhandled exceptions raised by an event handler. The catch clause raises the Application.ThreadException event. There is a default handler for that event, it displays a ThreadExceptionDialog. Giving the user the option to ignore the error or abort the program.

This catch clause is disabled when you run your program with a debugger. This allows you to easily debug exceptions. With it disabled, the CLR will find the catch clause in your Main() method. To disable this behavior, add this line of code to the top of your Main() method:

  Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);

It will now behave the same way as in the debugger. The better mousetrap here is to implement an event handler for AppDomain.CurrentDomain.UnhandledException. This catches all unhandled exceptions, including those raised in worker threads. And lets you debug unhandled exceptions, the debugger stops at the throw statement.

Hans Passant
Thank you for this insightful answer! Have I got it right that this is a means to generally handle ALL exceptions happening in any event handler's in my code? In particular to handle exceptions in button click events, menu click events, etc.? So far I surrounded every single one those event handlers with a try-catch block. This would then be unnecessary?
chiccodoro
`SetUnhandledExceptionMode` did it. For the event subscription approach I'll have to read a little bit more about it to understand how to use it...
chiccodoro