Я пытаюсь создать обработчик ошибок приложения, который разрешает любые необработанные исключения, но есть некоторые случаи нежелательного поведения, которые я не могу обойти.Как избежать каскадных сообщений об ошибках
Application_DispatcherUnhandledException
будет вызываться каждый раз, когда поток вне пользовательского интерфейса сталкивается с проблемами. Это, в свою очередь, вызовет App.HandleError
- статический метод, который будет регистрировать проблему, отображать сообщение пользователю, а если что-то критическое неверно, инициируйте закрытие приложения.
Моя основная проблема, похоже, когда что-то в xaml начинает генерировать исключения (например, исключение в DataTemplate или маршрутизированное событие). В большинстве случаев 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);
}
}
Они могли бы как-то отскакивать от границы, как пасьянсы. –
Где вы увеличиваете одновременные ошибки? – Paparazzi
Как просто хранить исключения в коллекции, а затем отображать одно настраиваемое всплывающее окно, содержащее 'ItemsControl' или' ListBox', связанное со списком исключений? – Rachel