Обработка исключений для событий - PullRequest
7 голосов
/ 26 марта 2010

Я прошу прощения, если это простой вопрос (мой Google-Fu сегодня может быть плохим).

Представьте себе это приложение WinForms, которое имеет такой дизайн: Главное приложение -> показывает одно диалоговое окно -> это1-й диалог может показать другой диалог.В обоих диалоговых окнах есть кнопки OK / Отмена (ввод данных).

Я пытаюсь выяснить какой-то тип глобальной обработки исключений в соответствии с Application.ThreadException.Я имею в виду:

В каждом из диалогов будет несколько обработчиков событий.2-й диалог может иметь:

private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
    try
    {      
        AllSelectedIndexChangedCodeInThisFunction();
    }
    catch(Exception ex)
    {
        btnOK.enabled = false;  // Bad things, let's not let them save
        // log stuff, and other good things
    }
}

Действительно, все обработчики событий в этом диалоге должны обрабатываться таким образом.Это исключительный случай, поэтому я просто хочу зарегистрировать всю соответствующую информацию, показать сообщение и отключить кнопку «ОК» для этого диалога.

Но я хочу избежать попытки / перехвата в каждом обработчике событий(если бы я мог).Недостаток всех этих try / catch заключается в следующем:

private void someFunction()
{
    // If an exception occurs in SelectedIndexChanged,
    // it doesn't propagate to this function
    combobox.selectedIndex = 3; 
}

Я не верю, что Application.ThreadException является решением, потому что я не хочу, чтобы исключение падало весь путь назад.до 1-го диалога, а затем основного приложения.Я не хочу закрывать приложение, я просто хочу зарегистрировать его, отобразить сообщение и позволить им выйти из диалога.Они могут решить, что делать оттуда (может быть, пойти куда-нибудь еще в приложении).

По сути, «глобальный обработчик» между первым диалогом и вторым (а затем, я полагаю, другим «глобальным обработчиком»)."между основным приложением и первым диалогом).

Ответы [ 6 ]

8 голосов
/ 26 марта 2010

Да, обработка по умолчанию Application.ThreadException была ошибкой. К сожалению, это была необходимая ошибка, которая не заставила сотни тысяч программистов сразу же обескуражить и отчаиваться, написав свое первое приложение для Windows Forms.

Исправление, которое вы рассматриваете, не является исправлением, оно имеет большой потенциал, чтобы сделать его еще хуже. Хотя пользователь, нажимающий кнопку «Продолжить» в диалоговом окне исключений, является сомнительным результатом, проглатывание исключений в глобальном обработчике исключений намного хуже.

Да, do написать обработчик замены для ThreadException. Пусть он отобразит значение e.Exception.ToString () в окне сообщения, чтобы пользователь имел некоторое представление о том, что произошло. Затем отключите электронную почту или добавьте в журнал ошибок, чтобы вы знали, что пошло не так. Затем вызовите Environment.FailFast (), чтобы больше нельзя было нанести ущерб.

Сделайте то же самое для AppDomain.CurrentDomain.UnhandledException. Это не получит большую часть тренировки.

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

1 голос
/ 26 марта 2010

Глобальная обработка исключений в приложении WinForms выполняется с использованием двух обработчиков: Application.ThreadException и AppDomain.CurrentDomain.UnhandledException. ThreadException перехватывает необработанные исключения в основном потоке приложения, тогда как CurrentDomain.UnhandledException перехватывает необработанные исключения во всех других потоках. Глобальная обработка исключений может использоваться для следующих целей: показ удобного для пользователя сообщения об ошибке, регистрация трассировки стека и другой полезной информации, очистка, отправка отчета об ошибке разработчику. После того, как необработанное исключение поймано, приложение должно быть прекращено. Вы можете перезапустить его, но невозможно исправить ошибку и продолжить, по крайней мере, в нетривиальных приложениях.

Глобальная обработка исключений не заменяет локальную обработку исключений, которую все еще следует использовать. Локальные обработчики исключений никогда не должны использовать catch Exception, потому что это эффективно скрывает ошибки программирования. Необходимо ловить только ожидаемые исключения в каждом случае. Любое непредвиденное исключение должно привести к сбою программы.

1 голос
/ 26 марта 2010

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

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

Обработка исключений может быть выполнена в родительском диалоге.

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

, например

MyDialog myDialog = new MyDialog();
myDialog.Init(//data for the user to choose/manipulate);
if(myDialog.ShowDialog() == DialogResult.OK)
{
try{
ProcessDialogData(myDialog.SomeDataObject);
}
catch(/*...*/}
}

НТН

1 голос
/ 26 марта 2010

Звучит так, как будто ты хочешь аспекты. PostSharp может помочь вам.

1 голос
/ 26 марта 2010

Вы можете использовать обработчик AppDomain.CurrentDomain.UnhandledException, чтобы перехватывать ошибки в основном потоке пользовательского интерфейса и обрабатывать их для каждого диалога. Из MSDN:

В приложениях, использующих Windows Формы, необработанные исключения в основной поток приложения вызывают Application.ThreadException событие быть воспитанным. Если это событие поведение по умолчанию таково, что необработанное исключение не закрыть приложение, хотя заявка оставлена ​​в неизвестном государство. В этом случае UnhandledException событие не поднял. Такое поведение можно изменить с помощью конфигурации приложения файл или с помощью Application.SetUnhandledExceptionMode способ изменить режим на UnhandledExceptionMode.ThrowException до события ThreadException обработчик подключен. Это относится только в основной ветке приложения. Событие UnhandledException поднято за необработанные исключения, добавленные в другие темы.

0 голосов
/ 18 июня 2014

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

В следующем ответе я приму две формы, A и B. A является родительской формой, которая открывает B в какой-то момент.

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


Дело 1:

  • B является модальным по отношению к A & mdash; то есть B блокирует пользовательский ввод для A;
  • вы хотите перехватить все необработанные исключения, вызванные B, в одном месте
  • вы хотите, чтобы B автоматически закрывалось, когда это вызывает необработанное исключение.
    (Если вы не хотите этого, обратитесь к делу 3 ниже.)
  1. Найдите раздел кода, где A открывает B. Например:

    using (var b = new formB(…))
    {
        b.ShowDialog();
    }
    
  2. Обернуть вызов в b.ShowDialog() в блоке try / catch. Неперехваченные исключения, вызванные обработчиком событий в B, будут всплывать здесь, поэтому здесь вы их обрабатываете:

    using (var b = new FormB(…))
    {
        try
        {
            formB.ShowDialog();
        }
        catch (…Exception ex)
        {
            MessageBox.Show("B made a boo-boo.");
            // don't bother doing something to B, since the end of the `using`
            // block, and the fact that execution has left `b.ShowDialog()`,
            // will force it to close and dispose.
        }
    }
    

Случай 2:

  • B является немодальным по отношению к A & mdash; то есть он не блокирует ввод в A;
  • вы хотите перехватить все необработанные исключения, вызванные B, в одном месте;
  • вы можете или не хотите, чтобы B автоматически закрывался, когда это вызывает необработанное исключение.
  1. Найдите раздел кода, в котором вы открываете B

    var b = new formB(…);
    b.Show();
    
  2. Измените этот код так, чтобы экземпляр B отображался в выделенном потоке STA. Кроме того, установите обработчик для Application.ThreadException в этом потоке и обработайте все необработанные исключения из B там:

    var thread = new Thread(() =>
    {
        Form b = null;
        Application.ThreadException =+ (sender, e) =>
        {
            Exception ex = e.Exception;
            MessageBox.Show("B made a booboo.");
            if (b != null)
            {
                … // do something with B here if you want;
                  // e.g. close it via a b.Close(),
                  // or force it to close via Application.ExitThread().
            }
        }
        b = new FormB();
        Application.Run(b);
    });
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    

    Возможно, вы захотите переместить шаблон кода в параметризованный вспомогательный метод, например, один с подписью, такой как void ShowAndCatch(Func<Form> formFactory, Action<Exception> exceptionHandler).


Дело 3:

Как и в случае 1, описанном выше, но вы не хотите, чтобы B автоматически закрывалось, когда оно генерирует необработанное исключение.

  1. Найдите точку входа в потоке, который открывает A. Если ваше приложение Windows Forms не является каким-то причудливым, это будет основной ("UI") поток, а его точка входа будет точкой входа приложения & mdash; например, static void Main(…):

    static void Main()
    {
        Application.Run(new FormA());
    }
    
  2. Установите обработчик для Application.ThreadException здесь и убедитесь, что необработанные исключения будут перенаправлены в этот обработчик событий:

    static void Main()
    {
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
        Application.ThreadException += (sender, e) =>
        {
            // The tricky part here is how to determine whether the exception
            // was triggered by B or by any other part of your application.
            // One possibly working technique might be:
            if (e.Exception.WasTriggeredByAFormB()) // see extension method below
            {
                MessageBox.Show("B made a boo-boo.");
                // Unfortunately, we are unlikely to have a direct reference to B
                // here. If it is guaranteed that there is only one form of type
                // FormB, you could retrieve it e.g. via:
                var b = Application.OpenForms.OfType<FormB>().SingleOrDefault();
                if (b != null)
                {
                    … // do something to b.
                }
            }
            else
            {
                … // other unhandled exception; perhaps do a Environment.FailFast(null);
            }
    
        }
        Application.Run(new FormA());
    }
    
    public static bool WasTriggeredByAFormB(this Exception exception)
    {
        return new StackTrace(e.Exception)
               .GetFrames()
               .Select(frame => frame.GetMethod().DeclaringType)
               .FirstOrDefault(type => typeof(Form).IsAssignableFrom(type))
               == typeof(FormB);
    }
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...