How to avoid cascading error messages

I'm trying to create an application error handler that resolves any unhandled exceptions, but there are some cases of unwanted behavior that I can't seem to work around.

Application_DispatcherUnhandledException

will be called when a thread outside the UI encounters a problem. This, in turn, will call App.HandleError

- a static method that will log the problem, display a message to the user, and if something critical is wrong, initiate an application shutdown.

My main problem seems to be when something in the xaml starts throwing exceptions (like an exception in the DataTemplate or a routed event). In most cases, WPF will simply try to generate a control that throws an exception many times, resulting in cascading error messages, and the application consumes all the CPU power until it crashes unceremoniously.

Cascading Error Messages

I thought I resolved this in an error handler by blocking that method or returning immediately if the method is already in the middle of execution, but this has two problems: first, if the same exception persists as soon as the user clicks OK ", and the unlocking execution ErrorHandler reappears. I need to somehow detect if we are in a cascading error state, so I can just initiate the application to close.

Another problem is that in the case where two or more separate threads generate different errors at the same time, I certainly don't want any solution to make an error for a cascading / unrecoverable error, and I don't want one of the errors to just ignore because the other was the first.

Any ideas? I've looked at things like using Interlocked.Increment on error counting using the lock () statement and caching the last few errors with timestamps, but they all seem to have drawbacks.

Here's my final try. I apologize for how fat it is, but I try to tackle several unique issues at the same time.

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 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);
    }
}

      

+2


source to share


1 answer


Thank you Rachel for your helpful input. I decided to go with storing them in a collection. I haven't gotten around to showing them in the custom popup menu, but I just handle errors sequentially and then pop a new one off the stack (if any). If there are too many errors on the stack, I assume we are in a cascading error and I concatenate the errors together in a single message and close the application.



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 });
}

      

0


source







All Articles