Magnus Lindhe
Magnus Lindhe

Reputation: 7327

Why does the inner exception reach the ThreadException handler and not the actual thrown exception?

I'm seeing some wierd behaviour when throwing exceptions and catching them in the Application.ThreadException event handler.

Basically whats happening in the sample below is that an exception is thrown in the DoWork event handler of a BackgroundWorker. The RunWorkerCompleted event handler rethrows a new exception with the original as the inner exception.

Why does the inner exception show up in the ThreadException event handler and not the acutal exception being thrown? If I do not provide an inner exception in the RunWorkerCompleted event handler, the correct exception will show up.

using System;
using System.Windows.Forms;
using System.ComponentModel;

namespace WierdExceptionApp
{
    class WierdExceptionForm : Form
    {
        BackgroundWorker worker = new BackgroundWorker();

        public WierdExceptionForm()
        {
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            worker.RunWorkerAsync();
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                throw new Exception("worker_RunWorkerCompleted", e.Error);
            }
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            throw new Exception("worker_DoWork");
        }

        [STAThread]
        static void Main()
        {
            Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
            Application.Run(new WierdExceptionForm());
        }

        static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            MessageBox.Show(e.Exception.Message);
        }
   }
}

Upvotes: 17

Views: 2755

Answers (2)

Hans Passant
Hans Passant

Reputation: 942109

The RunWorkerCompleted event is marshaled from the BGW thread to the UI thread by the WF plumbing that makes Control.Invoke() work. Essentially, there's a queue with delegates that is emptied by the message loop. The code that does this, Control.InvokeMarshaledCallbacks(), you'll see it on the call stack, has a catch (Exception) clause to catch unhandled exceptions. That clause calls Application.OnThreadException, passing the value of Exception.GetBaseException().

Well, that explains why you only see the inner exception. Why it is done this way is a bit unclear. Possibly to slice off the stack frames of the code in the UI thread that are otherwise pretty confusing since the real exception came from the background thread.

Upvotes: 13

liggett78
liggett78

Reputation: 11358

        if (e.Error != null)
        {
            throw new Exception("worker_RunWorkerCompleted", new Exception("Inner", new Exception("Inner inner")));
        }

You get "inner inner" at the end. It seems that this is the behavior of Application_ThreadException method to look at the inner-most exception.

Upvotes: 1

Related Questions