Головоломка цикла обработки исключений - PullRequest
15 голосов
/ 15 января 2010

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

Недавно я добавил обработку исключений в приложение как своего рода запасной вариант в случае необработанных исключений. Я в основном обрабатываю ThreadException и UnhandledException, как показано ниже:

// Add the event handler for handling UI thread exceptions to the event.
Application.ThreadException += new ThreadExceptionEventHandler(ExceptionHandler.OnUIThreadException);

// Set the unhandled exception mode to force all Windows Forms errors to go through
// our handler.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

// Add the event handler for handling non-UI thread exceptions to the event. 
AppDomain.CurrentDomain.UnhandledException +=
    new UnhandledExceptionEventHandler(ExceptionHandler.OnUnhandledException);

// Runs the application.
Application.Run(new ErrorHandlerForm());

Какой-то другой фрагмент кода, который я имел в приложении, уже перехватывал исключения - и поскольку у меня не было обработки исключений на месте, я просто перебрасывал исключение, чтобы убедиться, что оно не было проглочено:

//code in some method of the Program
try
{
   foo.SomeFooCall();
}
catch(Exception ex)
{
  logger.Log(ex.Message);
  // I don't swallow!
  throw;
}

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

Когда исключение генерируется где-то внутри вызова foo, оно явно перехватывается приведенным выше кодом, регистрируется, а затем снова генерируется. В этот момент включается ExceptionHandling, делает некоторые записи и уведомления (простой ящик сообщений), затем идет Application.Exit(). Далее происходит то, что приложение вернется к тому же throw, что вызовет обработку ошибок с теми же результатами, и это будет происходить несколько раз, пока не произойдет сбой, предположительно, потому что трассировка стека заполнена или каким-то образом обнаруживает бесконечный цикл.

РЕДАКТИРОВАТЬ: Выше приведено в режиме отладки - если я просто запустите его, он будет обрабатывать исключение один раз (показать окно сообщения, журнал и т. Д.), А затем просто сбой (я предполагаю, что переполнение стека ).

Я ожидаю, что ответ на этот вопрос может быть тривиальным (или я могу упустить что-то очевидное) - но любые указатели / объяснения будут высоко оценены.

EDIT: Методы обработчиков исключений переводят оба вызова в метод OnException, который выглядит примерно так:

private void OnUIThreadException(object sender, ThreadExceptionEventArgs e)
{
   OnException(e.Exception);
}

private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
   OnException((Exception)e.ExceptionObject);
}

private void OnException(Exception exception)
{
   MessageBox.Show("Fatal Exception: " + exception.Message);

   logger.Log(LoggingLevel.FATAL, "myLousyApp", exception.Message);

   Application.Exit();
}

На самом деле я делаю smt больше, чем просто - например, спрашиваю пользователя, хотят ли они перезапустить приложение, и, если это так, перезапускает его с идентификатором процесса в виде cmd arg, чтобы при перезапуске он ждал, пока старый процесс выход (он защищен от дубликатов экземпляров через мьютекс). Но для этого вопроса это не имеет значения, поскольку я не перезагружаю приложение, когда испытываю описанное поведение.

РЕДАКТИРОВАТЬ: Я создал еще одно простое приложение для воспроизведения этих условий - у меня есть простой компонент, который выбрасывает исключения (я выбрасываю произвольное число исключений в цикле), но во всех моих тестах на приложении Выход из приложения просто красиво завершается, и я не могу его воспроизвести. Озадаченный тем, что я должен искать!

Ответы [ 4 ]

13 голосов
/ 05 июня 2012

tl; dr: это отладчик . Отделись, и ты не получишь это странное поведение.


Хорошо. Я немного поэкспериментировал с новым проектом бренда Spankin 'и получил результат. Я начну с публикации кода, чтобы вы тоже могли присоединиться к веселью и увидеть его воочию.

Кодекс

Пожалуйста, напишите мне на почту (не связано)

Form1.cs

Example form
Вам понадобятся две кнопки на форме. Подпишите их соответствующим образом, чтобы было совершенно очевидно, что вы делаете.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        throw new InvalidOperationException("Exception thrown from UI thread");
    }

    private void button2_Click(object sender, EventArgs e)
    {
        new Thread(new ThreadStart(ThrowThreadStart)).Start();
    }

    private static void ThrowThreadStart()
    {
        throw new InvalidOperationException("Exception thrown from background thread");
    }
}

Program.cs

static class Program
{
    [STAThread]
    static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        Application.ThreadException += Application_ThreadException;
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        if (e.Exception != null)
        {
            MessageBox.Show(string.Format("+++ Application.ThreadException: {0}", e.Exception.Message));
        }
        else
        {
            MessageBox.Show("Thread exception event fired, but object was not an exception");
        }
    }

    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Exception ex = e.ExceptionObject as Exception;

        if (ex != null)
        {
            MessageBox.Show(string.Format("*** AppDomain.UnhandledException: {0}", ex.Message));
        }
        else
        {
            MessageBox.Show("Unhandled exception event fired, but object was not an exception");
        }
    }
}

Файл проекта

Отключить процесс хостинга , в противном случае AppDomain (и сам Forms) не будет выгружен между сеансами отладки, в результате чего строка Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false); выдает InvalidOperationException, если вы измените UnhandledExceptionMode аргумент между пробегами. РЕДАКТИРОВАТЬ: или вообще, если установлено CatchException

Это не является строго необходимым для этого расследования, но если вы собираетесь поиграть и изменить UnhandledExceptionMode - что, я думаю, вы, вероятно, сделаете, если будете запускать этот код самостоятельно - эта настройка спасет вас от душевной боли. Disable the hosting process

Тестирование

Внутри отладчика

Добавить в пользовательский интерфейс

  1. Нажмите Добавьте пользовательский интерфейс
  2. Получить помощник «необработанное исключение» в отладчике
  3. F5 для продолжения исполнения
  4. Появится диалоговое окно, указывающее, что обработчик приложения получил событие исключения из потока пользовательского интерфейса
  5. Нажмите OK
  6. Приложение не аварийно завершает работу, поэтому смойте и повторите.

Киньте в фоновый поток

  1. Клик Бросок в фоновом режиме
  2. Появится диалоговое окно, указывающее, что обработчик AppDomain получил событие исключения из фонового потока
  3. Получить помощник «необработанное исключение» в отладчике
  4. F5 для продолжения исполнения
  5. goto 2. На самом деле.

Здесь кажется, что обработчик AppDomain превосходит отладчик по любой причине. Однако после того, как AppDomain завершил работу с ним, отладчику удается обнаружить необработанное исключение. Но то, что происходит дальше, вызывает недоумение: обработчик AppDomain снова запускается. И опять. И снова, до бесконечности . Помещение точки останова в обработчик означает, что это не является рекурсивным (по крайней мере, не в .NET), поэтому , вероятно, не закончится переполнением стека. The background thread

Теперь давайте попробуем ...

Вне отладчика

Пользовательский интерфейс

Та же процедура, тот же результат - за исключением того, что помощник отладчика явно отсутствовал. Программа по-прежнему не аварийно завершала работу, потому что UnhandledExceptionMode.CatchException делает то, о чем говорит, и обрабатывает исключение «внутренне» (по крайней мере, в Forms) вместо передачи его в Feds AppDomain.

Фоновая нить

Теперь это интересно.

  1. Нажмите Бросьте в фоновом режиме
  2. Появится диалоговое окно, указывающее, что обработчик AppDomain получил событие исключения из фонового потока
  3. Получить диалог сбоя Windows
    Windows crash dialog
  4. Нажмите Debug, чтобы получить исключение при отладке JIT
  5. Получить помощник "необработанное исключение"
  6. F5 продолжить
  7. Выход из программы

Во-первых, домен приложений не зацикливается, как это происходит с подключенным отладчиком, а во-вторых, своевременное подключение отладчика из диалогового окна ошибки Windows не вызывает такое странное поведение.

Заключение

Кажется, что отладчик делает что-то странное в отношении необработанных исключений, попадающих в AppDomain. Я не знаю много о том, как отладчик делает свое волшебство, поэтому я не буду спекулировать, но цикл only происходит, когда отладчик подключен, поэтому, если цикл является проблемой, отсоединение является одним из обходных путей Вы могли бы использовать (возможно, используя Debugger.Launch(), чтобы дать себе время для повторного присоединения позже, если вам это нужно).

<3 </p>

7 голосов
/ 08 апреля 2013

Это просто опция VS, которая включена по умолчанию (актуально только при отладке, конечно).Так что нет необходимости отсоединять отладчик, если все, что вам нужно, это (протестировать?) Аварийно завершить работу и записать, где будет выполняться ваш не отлаженный процесс.

Where this behaviour is controlled

3 голосов
/ 15 января 2010

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

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

0 голосов
/ 15 января 2010

Я подозреваю, что в ExceptionHandler.OnUnhandledException может возникнуть новое исключение, которое остается необработанным, переходит в обработчик необработанных исключений, который вызывает ваш метод ExceptionHandler.OnUnhandledException и т. Д. (Бесконечный цикл -> переполнение стека).

Дважды проверьте метод OnUnhandledException на наличие выданных ошибок.

Редактировать: забавно, я пытаюсь воспроизвести вашу ошибку, но вместо того, чтобы снова и снова выдавать ошибку, мое приложение. просто выходит после необработанного метода обработчика исключений, независимо от того, что там происходит. Вы отлаживали или работали? Это может вызвать разницу.

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