Threadsicherheit & Propertys
-
Hallo zusammen ...
Ich versuche das Analysieren von Daten über einen Workerthread zu realisieren. Leider habe ich damit noch keinerlei Erfahrung. Die ObservableCollection wird im GUI Thread erstellt und soll nun ausgewertet werden. Wenn nun im DoWork-Thread auf bspw. eine Property der ObColl zugegriffen wird erhalte ich eine Exception (Threadsicherheit etc.).
Nach einigem Experimentieren bin ich bei folgendem Code gelandet, wobei ich mir aber sicher bin, das das nicht Sinn der Sache sein kann.
public double verlustSoll { get { //ThreadSicherheit if (DispatcherObject.Thread != Thread.CurrentThread) { double tempCurrent = (double)DispatcherObject.Invoke(new Func<Object>(() => GetValue(verlustSollProperty))); tempCurrent = (double)DispatcherObject.Invoke(new Func<Object>(() => GetValue(verlustSollProperty))); return tempCurrent; } return (double)GetValue(verlustSollProperty); } set { SetValue(verlustSollProperty, value); } }
Meine Frage ist nun: Macht das Sinn oder sehe ich das richtig, das ich den GUI Thread nun doch "bremse" ? Eigentlich bin ich mir sicher, frage aber besser noch mal die Profis unter euch.
-
Naja, bei einem
BackgroundWorker
geht es ja oft auch darum, dass man das GUI reaktiv lässt, heisst dieses während der Bearbeitung nicht einfriert. Das ist mit dieser Lösung, solange nicht zu oft (tausende Male pro Sekunde) auf das Property zugegriffen wird, schon gegeben.Wenn der Wert des Property sich allerdings während der Bearbeitung nicht verändern soll, dann ist da eher der falsche Weg. Sammle, bevor du den
BackgroundWorker
startest, alle Daten, welche dieser benötigt, und übergib sie ihm am Anfang.Bei
RunWorkAsync
kannst du einObject
migeben. DasDoWork
Event übergibt dir einDoWorkEventArgs
Objekt. Und dieses enthält dann dasArgument
.Sehr ähnlich funktioniert es in die andere Richtung.
DoWorkEventArgs
hat das PropertyResult
vom TypObject
, welchem du das Resultat übergeben kannst. Im EventRunWorkerCompleted
erhälst du dasRunWorkerCompletedEventArgs
Objekt, welches das PropertyResult
enthält.Grüssli
-
Wenn der Wert des Property sich allerdings während der Bearbeitung nicht verändern soll, dann ist da eher der falsche Weg. Sammle, bevor du den BackgroundWorker startest, alle Daten, welche dieser benötigt, und übergib sie ihm am Anfang.
Ich gehe davon aus, das ich das getan habe, und JA, ich möchte nur lesend auf die Property zugreifen:
//Daten erstellen lassen if (useWorker) { if (IMainWindow.chartIsResizing == true) return; List<object> parameter = new List<object>(); parameter.Add(((ComboBoxItem)chartViewModeCombo.SelectedItem).Content); parameter.Add(showingMode); // chartTimeScale parameter.Add(currentDataClass); if (GenerateDataCollWorker.IsBusy) GenerateDataCollWorker.CancelAsync(); else GenerateDataCollWorker.RunWorkerAsync(parameter); return; }
private void GenerateDataCollWorker_DoWork(object sender, DoWorkEventArgs e) { // parameter.Add ( ((ComboBoxItem)chartViewModeCombo.SelectedItem).Content ); // parameter.Add(showingMode); //chartTimeScale // parameter.Add(currentDataClass); List<object> parameter = e.Argument as List<object>; e.Result = GenerateData(parameter[2] as DataClass, (ChartTimeScaleMode)parameter[1], parameter[0] as String); }
private List<StatistikHelper> GenerateData(DataClass currentDataClass, ChartTimeScaleMode timeScaleMode, String viewMode) { PropertyInfo propertyInfo = currentDataClass.GetType().GetProperty("datenBuffer"); Func<DataClass, ObservableCollection<DataSubClass>> reflectionGet = FastInvoke.BuildTypedGetter<DataClass, ObservableCollection<DataSubClass>>(propertyInfo); ObservableCollection<DataSubClass> datenBuffer = reflectionGet.Invoke(currentDataClass); //Daten zusammenstellen //... //:: foreach (DataSubClass dsc in datenbuffer) { //mache dies und das double debug = dsc.verlustSoll --> Threadübergreifend --> Exception } }
-
Du sammelst eben nicht die Daten, welche der
BackgroundWorker
benötigt, sondern nur Referenzen auf die Objekte, welche die Daten enthalten. Ich spreche davon, dass du dieseVerlustSoll
Werte im voraus sammelst und dann übergibst. Also grundsätzlich Kopien der Werte. Damit du nicht mehr auf die GUI Objekte imBackgroundWorker
zugreifen musst.Wieso aber du den Wert
VerlustSoll
aus der GUI holst, ist sowieso noch so eine Frage. Eigentlich sollte dies im Modell gespeichert sein und man holt sich die Daten dort, bzw. kann sich unter Umständen auch eine Kopie von diesem Modell geben lassen, was man dann an denBackgroundWorker
übergibt.Gerade auch WPF mit MVVM fördert ein solches separates Vorgehen enorm.
Grüssli
-
Bring mich auf den richtigen Weg!
Im Hauptprogramm werden Datensätze geladen und mittels DataGrid in einer Tabelle angezeigt:
public static readonly DependencyProperty dataCollectionProperty = DependencyProperty.Register("dataCollection", typeof(ObservableCollection<DataClass>), typeof(MainWindow), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(dataCollection_PropertyChanged), null), null);
Die DataClass sieht so aus und enthält STAMMDATEN die ich zur Berechnung benötige:
class DataClass : DependencyObject { public DataClass() { DispatcherObject = Dispatcher.CurrentDispatcher; } public Dispatcher DispatcherObject; public static readonly DependencyProperty datenBufferProperty = DependencyProperty.Register("datenBuffer", typeof(ObservableCollection<DataSubClass>), typeof(DataClass), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(datenBuffer_PropertyChanged), null), null); public static readonly DependencyProperty verlustSollProperty = DependencyProperty.Register("verlustSoll", typeof(double), typeof(DataClass), new FrameworkPropertyMetadata(((double)0), FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(verlust_PropertyChanged), null), null); public ObservableCollection<DataSubClass> datenBuffer { ... }
In dieser Klasse sind die einzelnen Datensätze zu den STAMMDATEN hinterlegt
class DataSubClass : DependencyObject { public DataSubClass () { DispatcherObject = Dispatcher.CurrentDispatcher; } public event PropertyChangedEventHandler PropertyChanged; public Dispatcher DispatcherObject; #region *** Variablen *** public static readonly DependencyProperty datumProperty = DependencyProperty.Register("datum", typeof(DateTime), typeof(DataSubClass), new PropertyMetadata(new DateTime())); public static readonly DependencyProperty gießmengeProperty = DependencyProperty.Register("gießmenge", typeof(double), typeof(DataSubClass)); public static readonly DependencyProperty verpacktProperty = DependencyProperty.Register("verpackt", typeof(double), typeof(DataSubClass)); public static readonly DependencyProperty bruchmengeProperty = DependencyProperty.Register("bruchmenge", typeof(double), typeof(DataSubClass)); public static readonly DependencyProperty bezuckerungProperty = DependencyProperty.Register("bezuckerung", typeof(double), typeof(DataSubClass));
Wenn der User nun im DataGrid einen Stammdatensatz auswählt soll mit der Analyse des datenBuffers begonnen werden. Via BackgroundWorker. Diesem übergebe ich dann das Original Object der ausgewählten currentDataClass welche ja alle Infos enthält.
Der Worker selber soll nun durch die ICollection<DataSubClass> mit bspw. 1000 Einträgen iterieren und berechnen.
Bsp.: ergebnis = ((DataSubClass.Verpackt - DataClass.ZuckeranteilSoll - DataSubClass.Bruchmenge) - DataSubClass.Gießmenge) * 100 / DataSubClass.Gießmenge
Wenn ich nun keine Referenz auf die DataClass übergeben sollte, wie mach ich das dann ? Über einen Copy-Konstruktor der DataClass ? Was wäre der richtige Weg ?
-
Wieso ist
DataClass
undDataSubClass
jeweils vonDependencyObject
abgeleitet, bzw. wieso verwendest du dortDependencyProperty
? Würde es nicht auch mit der Implementierung desINotifyPropertyChanged
Interface gehen? Damit wären deine Properties vom GUI Thread unabhängig. Und wenn du sie während der Berechnung nicht veränderst, könntest du ohne Probleme lesend darauf zugreifen.Grüssli