Почему внутреннее исключение попадает на обработчик ThreadException, а не на исключение?

Я вижу какое-то странное поведение при бросании исключений и улавливании их в обработчике событий Application.ThreadException.

В основном, что происходит в примере ниже, это то, что исключение генерируется в обработчике события DoWork в BackgroundWorker. Обработчик события RunWorkerCompleted восстанавливает новое исключение с оригиналом как внутреннее исключение.

Почему внутреннее исключение появляется в обработчике события ThreadException, а не исключается acutal exception? Если я не предоставил внутреннее исключение в обработчике событий RunWorkerCompleted, появится правильное исключение.

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);
        }
   }
}

Ответы

Ответ 1

Событие RunWorkerCompleted маршалируется из потока BGW в поток пользовательского интерфейса WF-сантехникой, что делает работу Control.Invoke(). По существу, есть очередь с делегатами, которая опорожняется контуром сообщения. Код, который делает это, Control.InvokeMarshaledCallbacks(), вы увидите его в стеке вызовов, имеет предложение catch (Exception), чтобы поймать необработанные исключения. Это предложение вызывает Application.OnThreadException, передавая значение Exception.GetBaseException().

Хорошо, это объясняет, почему вы видите только внутреннее исключение. Почему так делается, это немного неясно. Возможно, отрезать фреймы стека кода в потоке пользовательского интерфейса, которые в противном случае довольно запутывают, поскольку реальное исключение исходило из фонового потока.

Ответ 2

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

В конце вы получите "внутреннее внутреннее". Похоже, что это поведение метода Application_ThreadException для просмотра самого внутреннего исключения.