Предотвращение сброса внешнего исключения при вызове из BeginInvoke - PullRequest
10 голосов
/ 19 декабря 2011

У меня есть обработчик для Application.ThreadException, но я обнаружил, что исключения не всегда передаются ему правильно.В частности, если я сгенерирую исключение с внутренним исключением из обратного вызова BeginInvoke, мой обработчик ThreadException не получит исключение external - он получит только inner исключение.

Пример кода:

public Form1()
{
    InitializeComponent();
    Application.ThreadException += (sender, e) =>
        MessageBox.Show(e.Exception.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
    var inner = new Exception("Inner");
    var outer = new Exception("Outer", inner);
    //throw outer;
    BeginInvoke(new Action(() => { throw outer; }));
}

Если я раскомментирую строку throw outer; и нажму кнопку, то в окне сообщения отобразится внешнее исключение (вместе с его внутренним исключением):

System.Exception: Outer ---> System.Exception: Inner
--- Конец внутренней трассировки стека исключений ---
в WindowsFormsApplication1.Form1.button1_Click (Отправитель объекта,EventArgs e) в C: \ svn \ trunk \ Code Base \ Source.NET \ WindowsFormsApplication1 \ Form1.cs: строка 55
в System.Windows.Forms.Control.OnClick (EventArgs e)
в System.Windows.Forms.Button.OnClick (EventArgs e)
в System.Windows.Forms.Button.OnMouseUp (MouseEventArgs mevent)
в System.Windows.Forms.Control.WmMouseUp (сообщение & m, кнопка MouseButtons, щелчки Int32)
в System.Windows.Forms.Control.WndProc (Сообщение & m)
в System.Windows.Forms.ButtonBase.WndProc (сообщение & m)
в System.Windows.Forms.Button.WndProc (сообщение & m)
в System.Windows.Forms.Control.ControlNativeWindow.OnMessage (сообщение & m)
в System.Windows.Forms.Control.ControlNativeWindow.WndProc (Message & m)
в System.Windows.Forms.NativeWindow.Callback (IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Но если throw outer; находится внутри вызова BeginInvoke, как в приведенном выше коде, то обработчик ThreadException only получает внутреннее исключение.Внешнее исключение удаляется до вызова ThreadException, и все, что я получаю, это:

System.Exception: Inner

(Нет стека вызововздесь, потому что inner никогда не получалось. В более реалистичном примере, где я поймал одно исключение и обернул его для повторного выброса, был бы стек вызовов.)

То же самое происходит, еслиЯ использую SynchronizationContext.Current.Post вместо BeginInvoke: внешнее исключение удаляется, а обработчик ThreadException получает только внутреннее исключение.

Я попытался обернуть больше слоев исключений вокруг, на случай, еслипросто удалял самое внешнее исключение, но это не помогло: очевидно, где-то есть цикл, выполняющий что-то вроде while (e.InnerException != null) e = e.InnerException;.

Я использую BeginInvoke, потому что у меня есть код, которыйнеобходимо сгенерировать необработанное исключение для немедленной обработки ThreadException, но этот код находится внутри блока catch выше стека вызовов (в частности, он находится внутри действия для Task, и Task будет перехватыватьисключение и остановить его распространение).Я пытаюсь использовать BeginInvoke, чтобы отложить throw до следующей обработки сообщений в цикле сообщений, когда я уже не внутри этого catch.Я не привязан к конкретному решению BeginInvoke;Я просто хочу выбросить необработанное исключение.

Как я могу вызвать исключение - включая его внутреннее исключение - для достижения ThreadException, даже когда я нахожусь внутри чьего-либо другого catch -все?

(я не могу вызвать мой метод-обработчик ThreadException напрямую из-за зависимостей сборки: обработчик перехватывается кодом запуска EXE-файла, в то время как моя текущая проблема находится на нижнем уровнеDLL.)

Ответы [ 3 ]

0 голосов
/ 19 декабря 2011

Рекомендованный способ распространения исключения на более высокий уровень (кроме неявного повторного вызова с помощью функции «Ожидание задачи») состоит в том, чтобы удалить универсальный элемент в теле задачи и вместо этого зарегистрировать продолжение сбоя в задаче с помощью Задача..Продолжить , указав TaskContinuationOptions.OnlyOnFaulted.Если вы работаете через промежуточный слой и не имеете доступа к Задаче, вы можете дополнительно обернуть это в свои собственные события UnhandledException, чтобы передать объект Exception вверх.

0 голосов
/ 20 декабря 2011

Один из способов сделать это - поместить ссылку на внутреннее исключение в пользовательское свойство или в словарь Data, т. Е. Оставить свойство InnerException пустым, и нести ссылку другим способом.

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

Пример кода (хотя ему нужно больше комментариев, чтобы объяснить, почему он делает сумасшедшие вещи):

public class ExceptionDecorator : Exception {
    public ExceptionDecorator(Exception exception) : base(exception.Message) {
        Exception = exception;
    }
    public Exception Exception { get; private set; }
}

// To throw an unhandled exception without losing its InnerException:
BeginInvoke(new Action(() => { throw new ExceptionDecorator(outer); }));

// In the ThreadException handler:
private void OnUnhandledException(object sender, ThreadExceptionEventArgs e) {
    var exception = e.Exception;
    if (exception is ExceptionDecorator)
        exception = ((ExceptionDecorator) exception).Exception;
    // ...
}
0 голосов
/ 19 декабря 2011

Я предполагаю, что вы наблюдаете такое поведение в системе x64 Windows, и это - довольно неизвестная деталь реализации Windows x64.Читайте об этом здесь

В статье подробно рассказывается о том, как решить эту проблему с помощью исправления, которое якобы поставлялось с Win7 SP1, но я столкнулся с этой проблемой несколько недельвернуться на Win7 SP1.

Кроме того, вы можете присоединить к Событие AppDomain.FirstChanceException , которое дает вам доступ к каждому исключению перед его передачей в CLR для обработки

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...