我試圖設計一個應用程序錯誤處理程序來解決任何未處理的異常,但有一些不受歡迎的行爲,我似乎無法解決。如何避免級聯錯誤消息
Application_DispatcherUnhandledException
將在UI以外的線程遇到麻煩時被調用。這又將調用App.HandleError
-一種靜態方法,它將記錄問題,向用戶顯示消息,如果關鍵錯誤,則啓動關閉應用程序。
我的主要問題似乎是xaml中的某些內容開始生成異常(例如DataTemplate或Routed Event中的異常)。在大多數情況下,WPF將一直試圖生成一遍又一遍地拋出異常的控件,從而導致級聯錯誤消息,並且應用程序會消耗所有處理器的能力,直到它不知不覺地崩潰。
我以爲我已經通過鎖定的方法解決了這個錯誤處理程序,或通過返回正確的路程,如果該方法已經在執行的中間,但這樣做有兩個問題 - 第一個是如果相同的異常繼續發生,只要用戶點擊「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);
}
}
他們能夠像紙牌卡一樣反彈離開邊界。 – 2012-03-27 15:04:43
你在哪裏增加SimultaneousErrors? – Paparazzi 2012-03-27 15:14:01
如何簡單地在一個集合中存儲異常,然後顯示一個包含綁定到異常列表的'ItemsControl'或'ListBox'的自定義彈出窗口? – Rachel 2012-03-27 15:15:14