2012-03-27 3 views
0

Normalerweise, wenn der UI-Thread etwas wie MessageBox.Show() ruft die aktuelle Codeausführung wird nicht fortgesetzt, bis der Benutzer auf OK klickt, aber das Programm wird auch weiterhin anderen Code auszuführen geschickt auf dem UI-Thread .Wie kann ich den UI-Thread auf einen Semaphor warten lassen, aber zusätzliche Dispatcher-Anfragen verarbeiten? (Wie das, was MessageBox.Show tut nativ)

In this question, hatte ich ein Problem mit zu vielen Delegierten auf dem UI-Thread geschickt wird sofort aufgerufen. Ich wollte an bestimmten Punkten pausieren, bevor ich die Ausführung fortsetzte.

In meiner neuen Fehlerbehandlung, verwende ich Semaphore nicht um sicherzustellen, dass mehr als ein Fehler auf einmal behandelt wird. Ich versende eine MessageBox, um den Benutzer zu benachrichtigen, und wenn sie auf "OK" klicken, lasse ich den Semaphor los, damit der nächste Fehler verarbeitet werden kann.

Das Problem ist, dass es wie erwartet nicht benimmt. Wenn zwei abgefragte Aufrufe an HandleError gleichzeitig erfolgen, leitet die erste einen Aufruf an MessageBox.Show ab, und die zweite blockiert den UI-Thread. Merkwürdigerweise wird der geschickte Aufruf MessageBox.Show() nie ausgeführt - die gesamte Anwendung hängt nur - so die Semaphore, der freigegeben werden soll ist, wenn der Benutzer auf „OK“ geklickt wird dauerhaft gesperrt. Was fehlt dieser Lösung?

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. 
    } 
} 

Mach dir keine Sorgen über die fehlende Zeichenkette, ich habe viele Details ausgeschnitten, um das Muster klarer zu machen.

+0

Haben Sie es mit anderen Werten von 'DispatcherPriority' versucht? –

Antwort

1

Unter der Annahme, dass das Verhalten Sie suchen, ist für jede Box Nachricht wiederum warten, bis die vorherige Meldung Feld gelöscht wurde, möchten Sie ein Muster wie folgt aus:

  1. Die Ereignisquelle die Nachrichtenwarteschlangen in eine Sperrwarteschlange
  2. die Ereignisquelle ruft einen Delegierten auf einem Hintergrund-Thread auf „die Warteschlangen-Prozeß“
  3. „verarbeiten der Queue“ delegieren nimmt eine Sperre (wie Sie getan haben), reiht eine Nachricht und ruft (synchron) zum UI-Thread, um die Nachricht anzuzeigen. Dann macht es eine Schleife und tut dasselbe, bis die Warteschlange leer ist.

So etwas wie diese (ungetestet Code voraus):

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

Danke für den wertvollen Beitrag. Ich konnte nicht ganz Ihre Lösung verwenden, da die Handhabung der Fehler einige komplexe Aktion erfordert getroffen werden, die auf dem UI-Thread geschehen hatte, aber du mich sicherlich auf die Antwort ließ ich verwendet, so Punkte für Sie. Meine Lösung ist unten. – Alain

1

Ich habe mit deren Speicherung in einer Sammlung beschlossen zu gehen, wie vorgeschlagen. Ich behandle einfach Fehler in der Reihenfolge und hole dann einen neuen aus dem Stapel (wenn es welche gibt). Wenn sich zu viele Fehler auf dem Stack aufbauen, gehe ich davon aus, dass wir uns in einer Kaskadierungsfehlersituation befinden und die Fehler in einer einzigen Nachricht zusammenfassen und die App herunterfahren.

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 }); 
} 
Verwandte Themen