我正在尝试设计一个应用程序错误处理程序来解决任何未处理的异常,但是在某些情况下我似乎无法解决这些不良行为。
只要UI外部的线程遇到麻烦,就会调用 Application_DispatcherUnhandledException
。这将依次调用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);
}
}
答案 0 :(得分:0)
谢谢Rachel,感谢您提供的帮助。我决定把它们存放在一个系列中。我没有在自定义弹出控件中显示它们,但只是按顺序处理错误然后从堆栈中弹出一个新的(如果有的话)。如果在堆栈上积累了太多错误,那么我假设我们处于级联错误状态,并且我在一条消息中将错误聚合在一起并关闭应用程序。
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 });
}