Laden...

WPF - OnPropertyChanged - Threadsafe machen - Welche Variante ist die Beste?

Erstellt von CSharpFreak vor 7 Jahren Letzter Beitrag vor 7 Jahren 3.583 Views
C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 7 Jahren
WPF - OnPropertyChanged - Threadsafe machen - Welche Variante ist die Beste?

Gute Abend 😃,

ich würde gerne eure Meinung zu diesen zwei Varianten hören, welche das DataBinding ThreadSafe machen sollen.
Oder ist das eine völlig falsche Herangehensweise an diesem Problem?

Variante 1:

        public event PropertyChangedEventHandler PropertyChanged;
		
        public virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
        {
            PropertyChangedEventHandler eventHandler = PropertyChanged;
            if (eventHandler != null)
            {
                Delegate[] delegates = eventHandler.GetInvocationList();
                foreach (PropertyChangedEventHandler handler in delegates)
                {
                    DispatcherObject DispatcherObject = handler.Target as DispatcherObject;

                    if (DispatcherObject != null && DispatcherObject.CheckAccess() == false)
                    {
                        DispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, new PropertyChangedEventArgs(propertyName));
                    }
                    else
                    {
                        handler(this, new PropertyChangedEventArgs(propertyName));
                    }
                }
            }
        }

Variante 2:

        public Dispatcher DispatcherObject { get; set; } = Dispatcher.CurrentDispatcher;
        
        public event PropertyChangedEventHandler PropertyChanged;
		
        public virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
          
            if (handler != null && DispatcherObject != null)
            {
                if (DispatcherObject.CheckAccess())
                {
					handler(this, new PropertyChangedEventArgs(propertyName));
                }
                else
                {
                   DispatcherObject.Invoke(() => handler(this, new PropertyChangedEventArgs(propertyName)));
                }
            }
        }

Grüße

5.299 Beiträge seit 2008
vor 7 Jahren

generell würde ich empfehlen, langlaufende Vorgänge vom Gui abzutrennen.
Also wenn in einer Schleife 10000 Werte berechnet werden, ists nicht sinnvoll, jedes einzelne Ergebnis via Databinding+thread-Delegation ans Gui zu melden - solch frisst ungeheuer Performance, und so schnell gucken kann auch niemand.

Hingegen in Einzelfällen mags auch mal sinnvoll sein, threadsicheres Databinding zu implementieren.

Ich würde aber eine effizientere Variante wählen, das mit dem Auspopeln der InvokationList, und dann jeden Abbonenten einzeln und mit Thread-Delegation invoken - das ist ziemlich lahm.

Der frühe Apfel fängt den Wurm.

D
985 Beiträge seit 2014
vor 7 Jahren

Halte ich für völlig falsch.

Schau dir mal Async Programming : Patterns for Asynchronous MVVM Applications: Data Binding an. Ich denke das ist es was du suchst.

5.299 Beiträge seit 2008
vor 7 Jahren

Was hälst du für völlig falsch?
Und warum - kannst du es vlt. kurz in eigenen Worten sagen?

Der frühe Apfel fängt den Wurm.

D
985 Beiträge seit 2014
vor 7 Jahren

Ja, das ViewModel stellt die Daten für die View und die läuft im UI Thread somit sollten die Eigenschaften im ViewModel ausschließlich im gleichen Thread gefüllt/verändert werden wie die zugehörige View.

Es gibt hinreichend Klassen/Verfahren um dieses schnell und einfach zu erreichen (async/await oder IProgress).

Die Frage ist also eher warum man sich den Stress ins ViewModel hineinholen sollte. Ich betrachte das ViewModel wie eine Abstraktion der View und behandle die Eigenschaften/Events so als ob ich die View direkt anfassen würde und dort arbeite ich in der Ebene auch immer im UI Thread.

74 Beiträge seit 2014
vor 7 Jahren

WPF macht es nichts aus, wenn PropertyChanged auf einem Hintergrundthread ausgelöst wird. Gebundene Controls werden automatisch auf dem UI-Thread geupdated. Ich finde jetzt leider keine offizielle Dokumentation darüber, aber ich kann bestätigen, dass es funktioniert. Siehe auch diesen StackOverflow Thread.

Man muss aber mit der ObservableCollection<T>-Klasse aufpassen, da muss man auf den UI-Thread, bevor man irgendwas damit macht.

W
955 Beiträge seit 2010
vor 7 Jahren

WPF macht es nichts aus, wenn PropertyChanged auf einem Hintergrundthread ausgelöst wird. Das funktioniert generell für Aufzählungen nicht. Bei List<T> und Kollegen ist das sogar gefährlich weil es manchmal funktioniert.

D
985 Beiträge seit 2014
vor 7 Jahren

Kommt auf das Timing an.

Das sollte funktionieren:


var list = new List<>();
list.Add(...);
vm.Collection = list;

Das kann in die Hose gehen:


var list = new List<>();
vm.Collection = list;
list.Add(...);

Dieses kann man aber ganz simpel per async/await lösen oder mit dem Konstrukt aus meinem Link.

Für mich ist das Schreiben der Properties in irgendeinem Hintergrund Thread keine Option.

C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 7 Jahren

Erst einmal vielen Dank für das ganze Feedback 😃

Der oben angegebene Code-Schnipsel war für das jeweilige ViewModel vorgesehen.

Dieses Model sieht dann nicht vor, dass eine 'infinity' Task Properties verändern kann/sollte?

D
985 Beiträge seit 2014
vor 7 Jahren

Dieser "infinity" Task würde ja im Model/Service laufen. Das ViewModel hängt sich dann als Subscriber da rein und verändert die Eigenschaft synchronisiert im passenden Thread Kontext.

Nur so hat man auch die Chance dort noch regulatorisch einzugreifen (wenn z.B. zu viele Änderungen pro Sekunde dort reinlaufen, dann übernimmt man nicht jeden Wert in die Anzeige, sondern nur maximal 10 Änderungen pro Sekunde).

C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 7 Jahren

Dieser "infinity" Task würde ja im Model/Service laufen. Das ViewModel hängt sich dann als Subscriber da rein und verändert die Eigenschaft synchronisiert im passenden Thread Kontext.

Nur so hat man auch die Chance dort noch regulatorisch einzugreifen (wenn z.B. zu viele Änderungen pro Sekunde dort reinlaufen, dann übernimmt man nicht jeden Wert in die Anzeige, sondern nur maximal 10 Änderungen pro Sekunde).

Der Task läuft im Model, das ist richtig.
Wie ist das mit dem Subscriber gemeint?

D
985 Beiträge seit 2014
vor 7 Jahren

Der Task oder die Aufgabe rödelt im Hintergrund herum und posaunt die gewonnene Erkenntnis in die Welt hinaus.

Das geht z.B. mit einem EventAggregator.

Jeder der sich für diese Events interessiert meldet sich an dem EventAggregator an (subscribe) und wird von nun an benachrichtigt. Bei einer geschickten Implementierung kann man beim Anmelden auch gleich den gewünschten Thread Kontext für diese Benachrichtigung auswählen/festlegen.