2010-03-26 2 views
7

Прошу прощения, если это простой вопрос (мой Google-Fu может быть плохим сегодня).C# - WinForms - Обработка исключений для событий

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

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

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

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

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

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

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

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

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

Спасибо.

ответ

1

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

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

1

Похоже, вы хотите аспектов. PostSharp может помочь вам.

1

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

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

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

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

например

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

НТН

+0

Как я уже говорил в другом месте, это приложение предприятие так что программа аудита (пользователь нажал на эту кнопку, и т.д.) и каротаж во многих наших мероприятиях. Это те вещи, которые, как я думаю, могут потерпеть неудачу. Вы правы, код SelectedIndexChanged (за исключением того, что я упоминал) редко терпит неудачу. Возможно, я выбрал плохое событие в моем вопросе. – JustLooking

1

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

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

7

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

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

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

Сделайте то же самое для AppDomain.CurrentDomain.UnhandledException. Это не займет много времени.

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

+0

Отличный отклик! – 2010-03-26 16:46:49

+1

Одно быстрое наблюдение: предположим, что вы регистрируетесь (что происходит практически во всех событиях), и это терпит неудачу. Чем ты занимаешься? Должен ли регистратор выкидывать исключение (и, таким образом, закрывать все приложение при попадании в глобальный обработчик)? Выводит ли ваш журнал исключение (или просто возвращает код ошибки)? Как определить, что ведение журнала завершилось неудачей? Мы не можем зарегистрировать его. Должен ли пользователь получить окно сообщения? Давно, да, но это все журналы и «аудит программы» (который идет в базу данных), которые меня волнуют. Вот почему у меня была привычка попробовать/поймать все события. – JustLooking

+0

@JustLooking: что вы делаете, когда вы путешествуете по шнуру питания и отсоединяете машину? Вы просите кого-нибудь переадресовать кабель, чтобы он не повторился. Да, окно сообщения. –

0

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

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

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


Случай 1:

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

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

    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 —, то есть он не блокирует ввод A;
  • вы хотите поймать все необработанные исключения, вызванные B в одном месте;
  • вы можете или не хотите, чтобы B автоматически закрывался, когда он вызывает необработанное исключение.
  1. Найдите раздел кода, где вы открываете B

    var b = new formB(…); 
    b.Show(); 
    
  2. Изменить этот код таким образом, что Б создан экземпляр показан на специальном 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») нить, и его точка входа будет запись приложения точка — например, 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); 
    } 
    
Смежные вопросы