How can I get the UI thread to wait on a semaphore, but process additional dispatcher requests? (like what MessageBox.Show does natively)

扶醉桌前 提交于 2019-12-02 11:04:28

Assuming that the behavior you are looking for is for each message box to wait in turn until the previous message box has been cleared, you want a pattern like this:

  1. The event source queues the message in a blocking queue
  2. The event source invokes a delegate on a background thread to "Process the Queue"
  3. The "Process the Queue" delegate takes a lock (as you have done), dequeues a message, and Invokes (synchronously) to the UI thread to show the message. Then it loops, doing the same thing until the queue is emtpy.

So something like this (untested code ahead):

private static ConcurrentQueue<Tuple<Exception, DateTime>> QueuedErrors = new ConcurrentQueue<Tuple<Exception, DateTime>>();
private static Object Lock_HandleError = new Object();
public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true)
{
    QueuedErrors.Enqueue(new Tuple<Exception, String>(ex, DateTime.Now));
    ThreadPool.QueueUserWorkItem(()=>((App)App.Current).Dispatcher.Invoke((Action)
            () => {
                lock (Lock_HandleError)
                    Tuple<Exception, DateTime> currentEx;
                    while (QueuedErrors.TryDequeue(out currentEx))
                        MessageBox.Show(
                           currentEx.Item1, // The exception
                           "MUS Application Error", 
                           MessageBoxButton.OK, 
                           MessageBoxImage.Error);
            }))
    );

I've decided to go with storing them in a collection as suggested. I am simply handling errors in sequence and then popping a new one off the stack (if there are any). If too many errors build up on the stack, then I assume we're in a cascading error situation and I aggregate the errors together in a single message and shut down the app.

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 });
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!