Я пытаюсь спроектировать обработчик ошибок приложения, который разрешает любые необработанные исключения, но есть некоторые случаи нежелательного поведения, которое я не могу обойти.
Application_DispatcherUnhandledException
будет вызываться всякий раз, когда поток за пределами пользовательского интерфейса сталкивается с проблемой. Это, в свою очередь, вызовет App.HandleError
- статический метод, который регистрирует проблему, отображает сообщение для пользователя, и, если что-то критично, запускает закрытие приложения.
Моя главная проблема, кажется, когда что-то в xaml начинает генерировать исключения (например, исключение в DataTemplate или Routed Event). В большинстве случаев WPF просто будет пытаться сгенерировать элемент управления, который будет выдавать исключение снова и снова, что приведет к каскадным сообщениям об ошибках и приложению, потребляющему всю мощность процессора до тех пор, пока оно не выйдет из строя.
Я думал, что решил эту проблему в обработчике ошибок, заблокировав метод или вернув его сразу, если метод уже находится в процессе выполнения, но это имеет две проблемы - во-первых, если возникает одно и то же исключение , как только пользователь нажимает «ОК» и выполнение 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);
}
}