2012-03-27 86 views
2

我試圖設計一個應用程序錯誤處理程序來解決任何未處理的異常,但有一些不受歡迎的行爲,我似乎無法解決。如何避免級聯錯誤消息

Application_DispatcherUnhandledException將在UI以外的線程遇到麻煩時被調用。這又將調用App.HandleError-一種靜態方法,它將記錄問題,向用戶顯示消息,如果關鍵錯誤,則啓動關閉應用程序。

我的主要問題似乎是xaml中的某些內容開始生成異常(例如DataTemplate或Routed Event中的異常)。在大多數情況下,WPF將一直試圖生成一遍又一遍地拋出異常的控件,從而導致級聯錯誤消息,並且應用程序會消耗所有處理器的能力,直到它不知不覺地崩潰。

Cascading Error Messages

我以爲我已經通過鎖定的方法解決了這個錯誤處理程序,或通過返回正確的路程,如果該方法已經在執行的中間,但這樣做有兩個問題 - 第一個是如果相同的異常繼續發生,只要用戶點擊「OK」並且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

他們能夠像紙牌卡一樣反彈離開邊界。 – 2012-03-27 15:04:43

+0

你在哪裏增加SimultaneousErrors? – Paparazzi 2012-03-27 15:14:01

+2

如何簡單地在一個集合中存儲異常,然後顯示一個包含綁定到異常列表的'ItemsControl'或'ListBox'的自定義彈出窗口? – Rachel 2012-03-27 15:15:14

回答

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