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.


  • Administrator

    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 ein Object migeben. Das DoWork Event übergibt dir ein DoWorkEventArgs Objekt. Und dieses enthält dann das Argument .

    Sehr ähnlich funktioniert es in die andere Richtung. DoWorkEventArgs hat das Property Result vom Typ Object , welchem du das Resultat übergeben kannst. Im Event RunWorkerCompleted erhälst du das RunWorkerCompletedEventArgs Objekt, welches das Property Result 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
    }
    
    }
    

  • Administrator

    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 diese VerlustSoll Werte im voraus sammelst und dann übergibst. Also grundsätzlich Kopien der Werte. Damit du nicht mehr auf die GUI Objekte im BackgroundWorker 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 den BackgroundWorker ü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 ?


  • Administrator

    Wieso ist DataClass und DataSubClass jeweils von DependencyObject abgeleitet, bzw. wieso verwendest du dort DependencyProperty ? Würde es nicht auch mit der Implementierung des INotifyPropertyChanged 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


Anmelden zum Antworten