Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
        }
   }
}
like image 670
Magnus Lindhe Avatar asked Dec 07 '08 11:12

Magnus Lindhe


2 Answers

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.

like image 189
Hans Passant Avatar answered Oct 11 '22 19:10

Hans Passant


        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.

like image 33
liggett78 Avatar answered Oct 11 '22 19:10

liggett78