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
BackgroundWorkergeht 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
BackgroundWorkerstartest, alle Daten, welche dieser benötigt, und übergib sie ihm am Anfang.Bei
RunWorkAsynckannst du einObjectmigeben. DasDoWorkEvent übergibt dir einDoWorkEventArgsObjekt. Und dieses enthält dann dasArgument.Sehr ähnlich funktioniert es in die andere Richtung.
DoWorkEventArgshat das PropertyResultvom TypObject, welchem du das Resultat übergeben kannst. Im EventRunWorkerCompletederhälst du dasRunWorkerCompletedEventArgsObjekt, welches das PropertyResultenthä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
BackgroundWorkerbenötigt, sondern nur Referenzen auf die Objekte, welche die Daten enthalten. Ich spreche davon, dass du dieseVerlustSollWerte im voraus sammelst und dann übergibst. Also grundsätzlich Kopien der Werte. Damit du nicht mehr auf die GUI Objekte imBackgroundWorkerzugreifen musst.Wieso aber du den Wert
VerlustSollaus 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
DataClassundDataSubClassjeweils vonDependencyObjectabgeleitet, bzw. wieso verwendest du dortDependencyProperty? Würde es nicht auch mit der Implementierung desINotifyPropertyChangedInterface 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