2012-03-27 5 views
2

처리되지 않은 예외를 해결하는 응용 프로그램 오류 처리기를 설계하려고하지만 주변에서 벗어날 수없는 바람직하지 않은 동작이있는 경우가 있습니다.계단식 오류 메시지가 나타나지 않도록하는 방법

Application_DispatcherUnhandledException은 UI 외부의 스레드가 문제가 발생할 때마다 호출됩니다. 그러면 App.HandleError으로 전화 할 것입니다.이 방법은 문제를 기록하고 사용자에게 메시지를 표시하며 중요한 문제가있는 경우 응용 프로그램을 종료하는 정적 방법입니다.

내 주요 문제는 xaml의 일부가 Exception (예 : 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

적어도 카드 점처럼 경계에서 벗어날 수 있습니다. –

+0

어디에서 증분 동시 오류입니까? – Paparazzi

+2

단순히 예외를 Collection에 저장 한 다음 예외 목록에 바인딩 된'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 }); 
} 
관련 문제