Laden...

Tasks.Parallel: Problem mit korrekter Datenzuordnung

Erstellt von oehrle vor 9 Jahren Letzter Beitrag vor 9 Jahren 2.045 Views
O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 9 Jahren
Tasks.Parallel: Problem mit korrekter Datenzuordnung

Hallo, in meiner Anwendung verarbeite ich eine unmenge von Dateien, die ich abgleichen muss. Ich werde unter anderem das Datum aus.

Nun habe ich mehrere Collections (FileInfo[] - genauer gesagt) die nacheinander abgearbeitet werden. Diese Bearbeitung läuft in einem Thread, damit man an anderen Dingen weiter arbeiten kann.
Nun wird jedes FileInfo[] mit seinen Dateien untersucht. Damit das schneller geht, versuchte ich die Parallel.Foreach().
Wenn ich diese aber verwende, geht die auswertung schief, Daten kommen durcheinander (da gibt's wohl das berüchtigte DataRace).
Ich verwende nun das lock(), aber es passiert trotzdem. Hat jemand einen Tipp, Hinweis?


foreach (DirectoryInfo directoryInfo in diCol.Where(x => x != null))
                            {
                                fi = directoryInfo.GetFiles();

                                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => lbx_Ablaufplan.Items.Insert(0, "Neue Dateien von " + directoryInfo.FullName + " suchen und in Sammelpool kopieren ...\n")));
                                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<ListBox>(lbx => lbx.Items.Insert(0, "\n\n\n--------------------------------------\n")), lbx_Ablaufplan);                   
                            

                                //// Progressbar neu initialiseren
                                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
                                    {
                                        pg_Bar.Value = 0;
                                        pg_Bar.Maximum = fi.Count();
                                    }));

                                //// Label für Dateizähler nullen
                                fileNummer = 0;

                                // INFO: Umbaumaßnahme (04.08.2014), damit nur noch GSS und GSV eingelesen werden, somit kommt keine PDF oder TXT-DAtei mehr dazu

                                #region PARALLEL-Verarbeitung
                                Parallel.ForEach(fi.Where(x=>x.Extension == ".GSS" || x.Extension == ".GSV"), fileInfo =>
                                {
                                    lock (fileInfo)
                                    {
                                        try
                                        {
                                            fileNummer++;

                                            //// Erstelldatum der Datei abfragen, für Vergleich aus Kopftabelle und Datei
                                            DateTime aenderungsdatum = new DateTime(fileInfo.CreationTime.Year, fileInfo.CreationTime.Month, fileInfo.CreationTime.Day, fileInfo.CreationTime.Hour, fileInfo.CreationTime.Minute, fileInfo.CreationTime.Second);


                                            //// Vergleich auf Name und Erstelldatum der Datei
                                            //if (SWD.DsProg.Tables[0].AsEnumerable().Where(i => i["Datei"].ToString().Trim() == fileInfo.Name.Trim() && Convert.ToDateTime(i["Dateidatum"].ToString()) < dateiErstelldatum).Count() > 0)
                                            //{
                                            DataRow vorhandenesFileInDatenfinder = SWD.DsProg.Tables[0].AsEnumerable().Where(i => i["Datei"].ToString().Trim() == fileInfo.Name.Trim()).FirstOrDefault();
                                            if (vorhandenesFileInDatenfinder == null || Convert.ToDateTime(vorhandenesFileInDatenfinder["Änderungsdatum"].ToString()) < aenderungsdatum)
                                            {
                                                //// Datei in den Sammelpool kopieren
                                                fileInfo.CopyTo(SWD.NameCol["sammelpool"] + "\\" + fileInfo.Name, true);
                                                //// Datei mit Pfad und Name der Liste zufügen
                                                _listUpdateDateien.Add(new string[] { fileInfo.Name, fileInfo.FullName, fileInfo.LastWriteTime.ToString() });

                                                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { lbx_Verarbeitet.Items.Insert(0, fileInfo.Name); }));
                                            }
                                            Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<ProgressBar, int>((p, v) => p.Value = v), pg_Bar, fileNummer);
                                            Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<Label>(lbl => lbl.Content = fileNummer + " von " + fi.Count()), lbl_AnzVerarbeitetVonX);
                                        }
                                        catch (IndexOutOfRangeException)
                                        { }
                                    }
                                });
                                #endregion
.
.
.
16.835 Beiträge seit 2008
vor 9 Jahren

Zunächst mal solltest Du vorsichtig sein bei parallelen Zugriffen auf Dateien. Das ganze kann nämlich schnell kontraproduktiv also werden, wenn Du parallel auf eine SSD / HDD zugreifen willst.
Es macht nur sinn, wenn Du die Daten bereits gelesen hast und nur noch die Objekte parallel bearbeiten willst; soviel zur Basis.
Dazu zählt auch das parallele Kopieren, was Du versuchst.

Als erstes solltest Du Deine Logik von der UI trennen.
Man verliert nicht nur schnell die Übersicht; das ganze wird auch auf Dauer unwartbar. Siehe dazu [Artikel] Drei-Schichten-Architektur

Du hast ein Objekt fileInfo, das die gesamte parallele Schleife lockt.
Das heisst, dass im Prinzip nichts parallel läuft, weil alle anderen Tasks auf den unlock warten. Deine parallele Schleife hättest Du Dir also einfach sparen können.
Absolut kein Zweck durch das lock();

Vermutlich ist der geteilte Context der FileInfo auch schuld an der Sache; könnte durchaus eine RaceCondition aufgrund der schlechten Umsetzung des Locks sein.

Je nachdem was der Sinne der gesamten Aufgabe ist lohnt sich ein Blick auf die sogenannten TPL Pipelines, also Consumer-Producer Pattern auf Task-Basis mit entsprechend geeigneten Collections.
Du hättest also mehrere Collections:

  • Eine Collection mit Suchtreffern - maximal ein Task oder Threads, wenn es sich um einen lokalen Speicherort handelt
  • Eine Collection zur Suche nach Deinen Kriterien - durchaus mehrere Task/Threads sinnvoll
  • Eine für das Kopieren - maximal ein Task oder Threads, wenn es sich um einen lokalen Speicherort handelt
    Aber da mir der Gesamtzweck hier unklar ist lass ich das mal offen.

Ich rate Dir den Business-Code von der UI zu trennen, die Logik unabhägnig von einer UI zu gestalten und im Prinzip von vorne anzufangen.

Convert.ToDateTime(vorhandenesFileInDatenfinder["Änderungsdatum"].ToString() wird auf anderen Systemen nicht funkionieren, weil dann pltzöich kein deutsches Format mehr heraus kommt, sondern zB englisches Format. Gewöhn Dir hier gleich an mit Kultur-unabhängigen Formaten zu arbeiten.

Wenn Du eine IndexOutOfRangeException bekommst spricht das für eine grundauf fehlerhafte Programmierung 😉

O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 9 Jahren

Hallo, danke für die Info (MVVM).
Also Speedtechnisch ist das immer noch sehr schnell, wesentlich schneller als nur sequentiell, aber halt falsch 😦

Normal müsste das fleInfo isoliert sein, und dann noch gelockt. Aber gut, evtl. hat noch jemand eine Idee. Und der Code sieht so wirklich unleserlich aus, da hast recht.

16.835 Beiträge seit 2008
vor 9 Jahren

Da läuft nichts parallel in Deinem Code. Null. Das verhinderst Du mit dem lock völlig.
Deine fileInfo ist nicht isoliert, weil andere Scopes immer noch drauf Zugriff haben. Du hast es nur innerhalb der Schleife gelockt, das heisst aber nicht, dass andere Elemente nicht darauf zugreifen können. Du müsstest an jeder Stelle ein lock() setzen, damit es Thread-sicher wäre.

Und darauf hoffen, dass noch jemand anders antwortet macht Deinen Code gewiss nicht besser; der sagts evtl nicht so direkt wie ich 😉
MVVM ist übrigens nicht die einzige 3-Schichten-Architektur; evtl mal den Link durchlesen, von MVVM im Speziellen ist in dem Link gar keine Rede - war nich umsonst 😉

Korrektur

Dein Code läuft parallel; ich hab übersehen, dass fileInfo aus der parallelen Schleife stammt.
Damit ist Dein lock aber immer noch unnütz; die Schleife läuft aber parallel durch. Nur Dein Lock bringt genau 0.

Lock-pflichtig ist zB SWD.DsProg.Tables[0] und listUpdateDateien und fileNummer .
Letzteres wird auch schuld an Deinem durcheinander sein. Es macht auch logisch keinen Sinn.
Nimm eine Parallel.For schleife und arbeite mit dessen index.
Du erhöhst fileNummer wahrscheinlich bereits obwohl Du an der unteren Stelle einfach noch den vorherigen Wert erwartest. Zudem ist ein Integer nicht Thread-sicher; dafür würde man mit Interlock arbeiten, wobei das aus logischer Sicht trotzdem falsch wäre - in diesem Fall ist Parallel.For angesagt.

Ich empfehl Dir auch die Basics der Threads und Tasks (nochmals) durchzuarbeiten.. [Artikel] Multi-Threaded Programmierung
Ohne Dir zu nahe treten zu wollen aber die Multi-Threaded-Programmierung kann - ähnlich wie hier - mehr Probleme schaffen als lösen, wenn man sie nicht von vorn bis hinten beherscht.
Damit das zuverlässig läuft musst Du das im Schlaf beherschen.