Normally, when the UI thread calls something like MessageBox.Show()
, the current code execution doesn't continue until the user clicks OK, but the program will continue to run other code dispatched on the UI thread.
In this question, I had a problem with too many delegates dispatched on the UI Thread being called at once. I wanted to pause at certain points before continuing execution.
In my new Error handler, I use semaphores to make sure no more than one error is being handled at once. I dispatch a MessageBox to alert the user, and when they click "OK", I release the semaphore, allowing the next error to be processed.
The problem is that it's not behaving as expected. If two dispatched calls to HandleError happen at the same time, the first one Dispatches a call to MessageBox.Show, and the second one blocks the UI Thread. Strangely, the dispatched call to MessageBox.Show()
never gets executed - the entire Application just hangs - so the semaphore that's supposed to be released when the user clicked "OK" is permanently locked. What is this solution lacking?
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 missing message string, I cut a lot of details out to make the pattern clearer.
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:
So something like this (untested code ahead):
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.