通常情况下,当UI线程调用像的MessageBox.show()
,目前的code执行不会继续下去,直到用户点击确定,但方案将继续运行其他code派出UI线程上。
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.
在this问题,我有太多的代表派遣的UI线程被调用时一旦出了问题。我想在某些地方继续执行之前暂停。
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.
现在的问题是,它不是按预期。如果两个分派呼叫的HandleError发生的同时,第一个调度呼叫的MessageBox.show,而第二个块中的UI线程。奇怪的是,到的MessageBox.show派出调用()
从来没有被执行 - 整个应用程序只是挂起 - 这样是应该的信号量,当用户点击确定被释放的永久锁定。这是什么产品所没有的?
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.
假设你正在寻找的行为是为每一个消息框,等待依次直到previous消息框已被清除,你想一个模式像这样的:
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:
在该事件源队列阻塞队列的消息 在该事件源调用在后台线程来过程中的队列委托 在进程队列授人需要一个锁(因为你这样做),取出一个消息,并调用(同步)到UI线程来显示信息。然后循环,做同样的事,直到队列为EMPTY的。因此,像这样(未经测试code未来):
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);
}))
);