2012-03-27 2 views
0

通常、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. 
    } 
} 

私はパターンをより明確にするために細部の多くを切り取る、奇数欠落しているメッセージ文字列を心配しないでください。

+0

「DispatcherPriority」の値を変えて試しましたか? –

答えて

1

あなたが探している行動は、前のメッセージボックスがクリアされるまで順番に待つために、各メッセージボックスのためであると仮定すると、あなたはこのようなパターンたい:

  1. をイベントソースは、メッセージをキューに入れますブロッキングキュー
  2. "キューを処理する"バックグラウンドスレッドでデリゲートを呼び出すイベントソース
  3. "キューを処理する"デリゲートは、(以前のように)ロックを取り、メッセージをデキューして同期的に呼び出します)をUIスレッドに渡してメッセージを表示します。それから、キューはemtpyになるまで同じことを繰り返します。

したがって、このような何か(先に未テストコード):

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); 
      })) 
    ); 
+0

貴重なご意見ありがとうございます。エラーを処理するには、UIスレッドで発生しなければならない複雑なアクションが必要ですが、私が使用した答えに私を教えてくれました。私の解決策は以下の通りです。 – Alain

1

私が示唆したように、コレクションに格納して行くことにしました。私は単純にエラーを順番に処理し、スタックから新しいものをポップします(もしあれば)。あまりにも多くのエラーがスタックに蓄積されている場合、私たちはカスケードエラー状態にあると仮定し、エラーをまとめて単一のメッセージにまとめ、アプリケーションをシャットダウンします。

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 }); 
} 
関連する問題