2012-04-12 7 views
1

Wie halten Sie Ihre Objekte threadsicher, die INotifyPropertyChanged implementieren? Ich kann keinen SynchronizationContext verwenden, weil ich das Objekt serialisieren muss.Wie Thread-Safe mit INotifyPropertyChanged ohne SynchronizationContext?

protected void OnPropertyChanged(string propertyName) 
    { 
     var handler = PropertyChanged; 
     if (handler != null) 
      // What can I add here to make it thread-safe? 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
    } 
+1

"Ich kann keinen SynchronizationContext verwenden, weil ich das Objekt serialisieren muss." - Das sollte kein Problem sein. Haben Sie das Feld mit '[NonSerialized]' markiert und eine 'OnDeserializing' Methode hinzugefügt, um es beim Deserialisieren zurückzusetzen? – hvd

+0

Ich habe nicht. Gibt es einen "einfacheren" Weg, als in meinem Code herumzugehen und den SynchronizationContext zu senden, wenn das Objekt erstellt wird?Oder bin ich einfach zu faul: D – Stephen

+0

Wenn Sie das Objekt in demselben Kontext erstellen, in dem die Ereignisbehandlungsroutinen ausgeführt werden sollen, können Sie das Feld im Konstruktor des Objekts auf "SynchronizationContext.Current" setzen. Ich bin mir nicht sicher, was der beste Weg wäre zu gehen. – hvd

Antwort

6

So ... stellt sich heraus, es ist eigentlich eine sehr schöne Art und Weise schließen, wenn Sie nicht auf einer Verlängerung unter Berufung nichts dagegen einige Code für Sie bei der Kompilierung zu erzeugen Zeit. Ich habe sowieso Fody/PropertyChanged benutzt, was dies sehr leicht macht. Dies vermeidet, einen Verweis auf eine SynchronizationContext in Modellen zu haben, die wirklich kein Business über die Benutzeroberfläche wissen.

  1. Zuerst installieren PropertyChanged.Fody, von NuGet zur Verfügung.

  2. Jede Klasse, die derzeit INofityPropertyChanged implementiert, sollte stattdessen das Attribut [ImplementPropertyChanged] haben. Die manuelle Implementierung sollte entfernt werden. Weitere Informationen finden Sie in den Artikeln readme und wiki.

  3. Ein PropertyChangedNotificationInterceptor muss implementiert werden. Sie stellen ein Beispiel für WPF im Wiki dar; meine Implementierung für ein WinForms Projekt:

    public static class PropertyChangedNotificationInterceptor 
    { 
        public static SynchronizationContext UIContext { get; set; } 
    
        public static void Intercept(object target, Action onPropertyChangedAction, string propertyName) 
        { 
         if (UIContext != null) 
         { 
          UIContext.Post(_ => 
          { 
           onPropertyChangedAction(); 
          }, null); 
         } 
         else 
         { 
          onPropertyChangedAction(); 
         } 
        } 
    } 
    
  4. Set PropertyChangedNotificationInterceptor.UIContext irgendwo. Sie können PropertyChangedNotificationInterceptor.UIContext = WindowsFormsSynchronizationContext.Current; in den Konstruktor Ihres Hauptformulars einfügen.

    Die Form constructor geht durch die Control constructor und wird schließlich am Ende WindowsFormsSynchronizationContext erstellen, wenn es noch nicht existiert. Daher ist es sicher, die .Current hier zu erfassen. (Anmerkung: Dies könnte eine Implementierung Detail sein, und in Zukunft .NET-Versionen, verschiedene Plattformen verändern könnte, usw.)

Beachten Sie, dass dies nur funktioniert, wenn Sie nur einen einzigen Sync-Kontext haben (ein einzelnes UI-Thread). Wenn Sie jemals in die Unordnung mehrerer UI-Threads geraten, und erfordern Datenbindung auf mehr als einer, würde dies viel komplizierter werden. Also bitte tu das nicht.

Dank Simon Cropp zum Schreiben PropertyChanged und hilft mir auch, diese besondere Funktion zu finden.

3

Wenn Sie WPF verwenden, können Sie Ihre Aufrufe an den UI-Thread mit dem Dispatcher marshalieren.

+0

Leider muss ich Formulare verwenden. – Stephen

+0

@Stephen Sie können den UI-Thread in WinForms aufrufen, indem Sie auf ein Steuerelement aufrufen, aber das ist klobig und nicht sehr elegant –

+1

Eine Stunde der Suche ... endlich - eine ausgezeichnete, saubere, prägnante und funktionierende Antwort (für WPF sowieso) . Vielen Dank! – Rob

4

Wenn Sie nicht glücklich sind und Winforms verwenden müssen, versuchen Sie MainForm Ihrer Anwendung zu verwenden, um den Handler im UI-Thread aufzurufen. Das Schlimme ist, dass Sie

using System.Windows.Forms;

protected void OnPropertyChanged(string propertyName) 
{ 
    var handler = PropertyChanged; 
    if (handler != null) 
    { 
     if (Application.OpenForms.Count == 0) return; 
     var mainForm = Application.OpenForms[0]; 
     if(mainForm == null) return; // No main form - no calls 

     if (mainForm.InvokeRequired) 
     { 
      // We are not in UI Thread now 
      mainform.Invoke(handler, new object[] { 
       this, new PropertyChangedEventArgs(propName)}); 
     } 
     else 
     { 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     }    
    } 
}