How can I make the UI thread wait for the semaphore but still handle additional dispatcher requests? (like what MessageBox.Show does initially)

Typically, when the UI thread calls something like MessageBox.Show()

, the current code execution does not continue until the user types OK, but the program will continue to execute other code posted on the UI thread.

In this question , I am having a problem with too many delegates sent on a UI topic being called at the same time. I wanted to pause at certain points before continuing.

In my new error handler, I use semaphores to ensure that no more than one error is handled at a time. I send a MessageBox to alert the user, and when they click OK, I release the semaphore, allowing the next error to be handled.

The problem is, it doesn't behave as expected. If two HandleError calls are dispatched at the same time, the first one dispatches a MessageBox.Show call, and the second one blocks the UI thread. Oddly enough, the call is MessageBox.Show()

never made - the whole application just hangs - so the semaphore that should be released when the user pressed OK is permanently blocked. What solution is missing?

private static ConcurrentDictionary<Exception, DateTime> QueuedErrors = new ConcurrentDictionary<Exception, DateTime>();
private static Semaphore Lock_HandleError = new Semaphore(1, 1); //Only one Error can be processed at a time
private static void ErrorHandled(Exception ex)
{
    DateTime value;
    QueuedErrors.TryRemove(ex, out value);
    Lock_HandleError.Release();
}

private static bool ExceptionHandlingTerminated = false;
public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true)
{
    if( ExceptionHandlingTerminated || App.Current == null) return;
    QueuedErrors.TryAdd(ex, DateTime.Now); //Thread safe tracking of how many simultaneous errors are being thrown

    Lock_HandleError.WaitOne(); //This will ensure only one error is processed at a time.

    if( ExceptionHandlingTerminated || App.Current == null )
    {
        ErrorHandled(ex);
        return;
    }

    try
    {
        if( QueuedErrors.Count > 10 )
        {
            ExceptionHandlingTerminated = true;
            throw new Exception("Too many simultaneous errors have been thrown in the background.");
        }

        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)
                {
                    ErrorHandled(_ex); //Release the semaphore taken by the spawning HandleError call
                    HandleError(_ex, _extraInfo, _showMsgBox, _resetApplication);
                }, DispatcherPriority.Background, new object[] { ex, extraInfo, showMsgBox, resetApplication });
            return;
        }

        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.
            Dispatcher.CurrentDispatcher.BeginInvoke((Action<Exception, String>)delegate(Exception _ex, String _ErrMessage)
            {
                MessageBox.Show(_ErrMessage, "MUS Application Error", MessageBoxButton.OK, MessageBoxImage.Error);
                ErrorHandled(_ex); //Release the semaphore taken by the spawning HandleError call
            }, DispatcherPriority.Background, new object[]{ ex, extraInfo });
        }
        else
        {
            ErrorHandled(ex);
        }
    }
    catch( Exception terminatingError )
    {
        ExceptionHandlingTerminated = true;
        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 });
        ErrorHandled(ex); //Release the semaphore taken by this HandleError call which will allow all other queued HandleError calls to continue and check the ExceptionHandlingTerminated flag.
    }
}

      

Don't worry about the odd line of the message, I've cut out a lot of details to make the template clearer.

0


source to share


2 answers


Assuming the behavior you are looking for is for each message box in the queue until the previous message box is cleared, you want to create a pattern like this:

  • Event source enqueues a message in the blocking queue
  • The event source calls a delegate on a background thread to "Process Queue"
  • The Process Queue delegate takes the lock (as you did), deactivates the message, and calls (synchronously) the thread thread to display the message. He then loops, doing the same 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);
            }))
    );

      

+1


source


I decided to go with saving them to the collection as suggested. I just handle errors in sequence 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 });
}

      

+1


source







All Articles