通常、UIスレッドがMessageBox.Show()
のようなものを呼び出すと、ユーザーが[OK]をクリックするまで現在のコードの実行は継続されませんが、プログラムはUIスレッドで送出される他のコードを実行し続けます。どのようにしてセマフォを待つようにUIスレッドを取得できますか?ディスパッチャの追加要求を処理できますか? (MessageBox.Showがネイティブで行うような)
this questionには、UIスレッドで一度に呼び出されるデリゲートが多すぎるという問題がありました。実行を続ける前に、ある時点で中断したかったのです。
私の新しいエラーハンドラでは、セマフォを使用して、一度に複数のエラーを処理しないようにしています。ユーザーに警告するためにMessageBoxをディスパッチし、「OK」をクリックすると、セマフォを解放し、次のエラーを処理できるようにします。
問題は、期待どおりに動作していないことです。 HandleErrorへの2つのディスパッチされた呼び出しが同時に発生した場合、最初のものはMessageBox.Showへの呼び出しを送出し、2番目の呼び出しはUIスレッドをブロックします。不思議なことに、MessageBox.Show()
へのディスパッチされたコールは決して実行されません。アプリケーション全体がちょうどハングするので、ユーザーが「OK」をクリックしたときに解放されるはずのセマフォは永久にロックされます。このソリューションには何が欠けていますか?
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.
}
}
私はパターンをより明確にするために細部の多くを切り取る、奇数欠落しているメッセージ文字列を心配しないでください。
「DispatcherPriority」の値を変えて試しましたか? –