2012-03-27 2 views
2

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

Application_DispatcherUnhandledException будет вызываться каждый раз, когда поток вне пользовательского интерфейса сталкивается с проблемами. Это, в свою очередь, вызовет App.HandleError - статический метод, который будет регистрировать проблему, отображать сообщение пользователю, а если что-то критическое неверно, инициируйте закрытие приложения.

Моя основная проблема, похоже, когда что-то в xaml начинает генерировать исключения (например, исключение в DataTemplate или маршрутизированное событие). В большинстве случаев WPF будет просто пытаться генерировать элемент управления, который много раз перебрасывает исключение, приводя к каскадным сообщениям об ошибках, а приложение потребляет всю мощность процессора, пока не сработает бесцеремонно.

Cascading Error Messages

Я думал, что я решил это в обработчик ошибок, блокируя метод, или путем возвращения сразу же, если этот метод уже в середине выполнения, но есть две проблемы - во-первых, что если одно и то же исключение сохраняется, как только пользователь нажимает «ОК», и выполнение разблокировки ErrorHandler снова появляется. Мне нужно каким-то образом определить, находимся ли мы в каскадном состоянии ошибки, поэтому я могу просто инициировать закрытие приложения.

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

Любые идеи? Я рассматривал такие вещи, как использование Interlocked.Increment на счету ошибок, используя оператор lock() и кэширование последних нескольких ошибок с помощью штампов времени, но все они, похоже, имеют недостатки.

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

private bool DispatchedErrorsLock = false; 
private void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) 
{ 
    //Prevent Recursion 
    e.Handled = true; 
    if(DispatchedErrorsLock || ExceptionHandlingTerminated) return; 
    DispatchedErrorsLock = true; 

    bool handleSilently = false; 
    //Ensures that minor xaml errors don't reset the application 
    if("PresentationFramework,PresentationCore,Xceed.Wpf.DataGrid.v4.3".Split(',').Any(s => e.Exception.Source.Contains(s))) 
    { 
     handleSilently = true; 
    } 

    HandleError(e.Exception, "Exception from external thread.", !handleSilently, !handleSilently); 
    DispatchedErrorsLock = false; 
} 

private static int SimultaneousErrors = 0; 
private static bool ExceptionHandlingTerminated = false; 
public static void HandleError(Exception ex, bool showMsgBox) { HandleError(ex, "", showMsgBox, true); } 
public static void HandleError(Exception ex, string extraInfo, bool showMsgBox) { HandleError(ex, extraInfo, showMsgBox, true); } 
public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true) 
{ 
    if(ExceptionHandlingTerminated || App.Current == null) return; 
    Interlocked.Increment(ref SimultaneousErrors); //Thread safe tracking of how many errors are being thrown 
    if(SimultaneousErrors > 3) 
    { 
     throw new Exception("Too many simultaneous errors have been thrown."); 
    } 

    try 
    { 
     if(Thread.CurrentThread != Dispatcher.CurrentDispatcher.Thread) 
     { 
      //We're not on the UI thread, we must dispatch this call. 
      ((App)App.Current).Dispatcher.BeginInvoke((Action<Exception, string, bool, bool>) 
       delegate(Exception _ex, string _extraInfo, bool _showMsgBox, bool _resetApplication) 
       { 
        Interlocked.Decrement(ref SimultaneousErrors); 
        HandleError(_ex, _extraInfo, _showMsgBox, _resetApplication); 
       }, DispatcherPriority.Background, new object[] { ex, extraInfo, showMsgBox, resetApplication }); 
      return; 
     } 

     if(!((App)App.Current).AppStartupComplete) 
     { //We can't handle errors the normal way if the app hasn't started yet. 
      extraInfo = "An error occurred before the application could start." + extraInfo; 
      throw ex; //Hack: Using throw as a goto statement. 
     } 

     String ErrMessage = string.Empty; 
     if(string.IsNullOrEmpty(extraInfo) && showMsgBox) 
      ErrMessage += "An error occurred while processing your request. "; 
     else 
      ErrMessage += extraInfo; 

     if(!showMsgBox && !resetApplication) 
      ErrMessage += " This error was handled silently by the application."; 

     //Logs an error somewhere. 
     ErrorLog.CreateErrorLog(ex, ErrMessage); 

     if(showMsgBox) 
     { 
      ErrMessage += "\nTechnical Details: " + ex.Message; 
      Exception innerException = ex.InnerException; 
      while(innerException != null) 
      { //Add what is likely the more informative information in the inner exception(s) 
       ErrMessage += " | " + ex.InnerException.Message; 
       innerException = innerException.InnerException; 
      } 
     } 

     if(resetApplication) 
     { 
      //Resets all object models to initial state (doesn't seem to help if the UI gets corrupted though) 
      ((MUS.App)App.Current).ResetApplication(); 
     } 
     if(showMsgBox) 
     { 
      //IF the UI is processing a visual tree event (such as IsVisibleChanged), it throws an exception when showing a MessageBox as described here: http://social.msdn.microsoft.com/forums/en-US/wpf/thread/44962927-006e-4629-9aa3-100357861442 
      //The solution is to dispatch and queue the MessageBox. We must use BeginInvoke() because dispatcher processing is suspended in such cases, so Invoke() would fail.. 
      Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate() 
      { 
       MessageBox.Show(ErrMessage, "MUS Application Error", MessageBoxButton.OK, MessageBoxImage.Error); 
       Interlocked.Decrement(ref SimultaneousErrors); 
      }, DispatcherPriority.Background); 
     } 
     else 
     { 
      Interlocked.Decrement(ref SimultaneousErrors); 
     } 
    } 
    catch(Exception e) 
    { 
     Interlocked.Decrement(ref SimultaneousErrors); 
     ExceptionHandlingTerminated = true; 
     //A very serious error has occurred, such as the application not loading or a cascading error message, and we must shut down. 
     String fatalMessage = String.Concat("An error occurred that the application cannot recover from. The application will have to shut down now.\n\nTechnical Details: ", extraInfo, "\n", e.Message); 
     //Try to log the error, but in extreme cases, there's no guarantee logging will work. 
     try { ErrorLog.CreateErrorLog(ex, fatalMessage); } 
     catch(Exception) { } 
     Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate() 
     { 
      MessageBox.Show(fatalMessage, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Stop); 
      if(App.Current != null) App.Current.Shutdown(1); 
     }, DispatcherPriority.Background); 
    } 
} 
+0

Они могли бы как-то отскакивать от границы, как пасьянсы. –

+0

Где вы увеличиваете одновременные ошибки? – Paparazzi

+2

Как просто хранить исключения в коллекции, а затем отображать одно настраиваемое всплывающее окно, содержащее 'ItemsControl' или' ListBox', связанное со списком исключений? – Rachel

ответ

0

Спасибо, Рейчел, за полезный вход. Я решил пойти с их хранением в коллекции. Я не дошел до их отображения в пользовательском всплывающем меню, но просто обрабатываю ошибки последовательно, а затем вывожу новый из стека (если они есть). Если в стеке возникает слишком много ошибок, я предполагаю, что мы находимся в каскадной ошибке, и я объединяю ошибки вместе в одном сообщении и закрываю приложение.

private static ConcurrentStack<Tuple<DateTime, Exception, String, bool, bool>> ErrorStack = new ConcurrentStack<Tuple<DateTime, Exception, String, bool, bool>>(); 
private static bool ExceptionHandlingTerminated = false; 
private static bool ErrorBeingHandled = false; //Only one Error can be processed at a time 

public static void HandleError(Exception ex, bool showMsgBox) { HandleError(ex, "", showMsgBox, true); } 
public static void HandleError(Exception ex, string extraInfo, bool showMsgBox) { HandleError(ex, extraInfo, showMsgBox, true); } 
public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true) 
{ 
    if(ExceptionHandlingTerminated || App.Current == null) return; 
    if(ErrorBeingHandled) 
    { //Queue up this error, it'll be handled later. Don't bother if we've already queued up more than 10 errors, we're just going to be terminating the application in that case anyway. 
     if(ErrorStack.Count < 10) 
      ErrorStack.Push(new Tuple<DateTime, Exception, String, bool, bool>(DateTime.Now, ex, extraInfo, showMsgBox, resetApplication)); //Thread safe tracking of how many simultaneous errors are being thrown 
     return; 
    } 

    ErrorBeingHandled = true; 
    try 
    { 
     if(Thread.CurrentThread != Dispatcher.CurrentDispatcher.Thread) 
     { 
      ErrorBeingHandled = false; 
      Invoke_HandleError(ex, extraInfo, showMsgBox, resetApplication); 
      return; 
     } 
     if(ErrorStack.Count >= 5) 
     { 
      ExceptionHandlingTerminated = true; 
      Tuple<DateTime, Exception, String, bool, bool> errParams; 
      String errQueue = String.Concat(DateTime.Now.ToString("hh:mm:ss.ff tt"), ": ", ex.Message, "\n"); 
      while(ErrorStack.Count > 0) 
      { 
       if(ErrorStack.TryPop(out errParams)) 
       { 
        errQueue += String.Concat(errParams.Item1.ToString("hh:mm:ss.ff tt"), ": ", errParams.Item2.Message, "\n"); 
       } 
      } 
      extraInfo = "Too many simultaneous errors have been thrown in the background:"; 
      throw new Exception(errQueue); 
     } 

     if(!((App)App.Current).AppStartupComplete) 
     { //We can't handle errors the normal way if the app hasn't started yet. 
      extraInfo = "An error occurred before the application could start." + extraInfo; 
      throw ex; 
     } 

     if(resetApplication) 
     { 
      ((MUSUI.App)App.Current).ResetApplication(); 
     } 
     if(showMsgBox) 
     { 
      //(removed)... Prepare Error message 

      //IF the UI is processing a visual tree event (such as IsVisibleChanged), it throws an exception when showing a MessageBox as described here: http://social.msdn.microsoft.com/forums/en-US/wpf/thread/44962927-006e-4629-9aa3-100357861442 
      //The solution is to dispatch and queue the MessageBox. We must use BeginInvoke because dispatcher processing is suspended in such cases. 
      Dispatcher.CurrentDispatcher.BeginInvoke((Action<Exception, String>)delegate(Exception _ex, String _ErrMessage) 
      { 
       MessageBox.Show(App.Current.MainWindow, _ErrMessage, "MUS Application Error", MessageBoxButton.OK, MessageBoxImage.Error); 
       ErrorHandled(_ex); //Release the block on the HandleError method and handle any additional queued errors. 
      }, DispatcherPriority.Background, new object[]{ ex, ErrMessage }); 
     } 
     else 
     { 
      ErrorHandled(ex); 
     } 
    } 
    catch(Exception terminatingError) 
    { 
     ExceptionHandlingTerminated = true; 
     //A very serious error has occurred, such as the application not loading, and we must shut down. 
     Dispatcher.CurrentDispatcher.BeginInvoke((Action<String>)delegate(String _fatalMessage) 
     { 
      MessageBox.Show(_fatalMessage, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Stop); 
      if(App.Current != null) App.Current.Shutdown(1); 
     }, DispatcherPriority.Background, new object[] { fatalMessage + "\n" + terminatingError.Message }); 
    } 
} 

//The set of actions to be performed when error handling is done. 
private static void ErrorHandled(Exception ex) 
{ 
    ErrorBeingHandled = false; 

    //If other errors have gotten queued up since this one was being handled, or remain, process the next one 
    if(ErrorStack.Count > 0) 
    { 
     if(ExceptionHandlingTerminated || App.Current == null) return; 
     Tuple<DateTime, Exception, String, bool, bool> errParams; 
     //Pop an error off the queue and deal with it: 
     ErrorStack.TryPop(out errParams); 
     HandleError(errParams.Item2, errParams.Item3, errParams.Item4, errParams.Item5); 
    } 
} 

//Dispatches a call to HandleError on the UI thread. 
private static void Invoke_HandleError(Exception ex, string extraInfo, bool showMsgBox, bool resetApplication) 
{ 
    ((App)App.Current).Dispatcher.BeginInvoke((Action<Exception, string, bool, bool>) 
     delegate(Exception _ex, string _extraInfo, bool _showMsgBox, bool _resetApplication) 
     { 
      ErrorHandled(_ex); //Release the semaphore taken by the spawning HandleError call 
      HandleError(_ex, _extraInfo, _showMsgBox, _resetApplication); 
     }, DispatcherPriority.Background, new object[] { ex, extraInfo, showMsgBox, resetApplication }); 
} 
Смежные вопросы