Hallo, zusammen, ich brauche mal eine Rat der Community.
Also, ich habe einen Algorhytmus, der Dateien in einem Verzeichnis analysiert und Daten in eine Datentabelle schreibt. Die Dateien können sich täglich ändern, neue dazu, alte raus, werden editiert usw. Das stelle ich alles fest und aktualisiere somit die Datentabelle. Nun braucht die Verarbeitung nun mehrere Minuten, und ich würde das gerne beschleunigen, hatte auch schon mit der Ablaufoptimierung einiges erreicht.
Nun vermute ich, das ich weitere Zeit gutmachen könnte, wenn ich den Prozess parallelisiere. Ich bin daran, das ich den Prozess aufteile, und zwar anhand der Anzahl der Prozessorkerne die der Rechner vorweisen kann. Dann hätte ich das so gemacht, das ich die Verarbeitun in einer Methode mache. Diese Methdoe dient dann für die Anzahl der Tasks = Prozessorkerne und lasse die dann alle abarbeiten (parallel) und warte dann darauf das alle Tasks abgearbeitet sind. Ichhabe mal so einBeispielcode erstellt, wie das unefähr sein soll.
Wo ich mir nicht sicher bin, darf ich die identische Methode in zig Tasks gleichzeitig verwenden, oder macht das Probleme? Die Daten die in der Methode verarbeitet werden sollten so wie ich die übergebe voneinander unabhängig sein und nichts mit lock() gesperrt werden.
/* ========================================================================================================================================================================*/
/// <summary>
/// Test um eine Bearbeitungsroutine auf mehrere Tasks gleichmäßig aufzuteilen und parallel ablaufen zu lassen
/// </summary>
private void TaskTestle()
{
int startWertVeraenderlich = 0;
int teilmenge = tblDatensaetze.Rows.Count / Environment.ProcessorCount;
List<Task> lstTasksZuLoeschendeDatensaetze = new List<Task>();
for (int prozNr = 0; prozNr < Environment.ProcessorCount; prozNr++)
{
//// Startwert für den Bereich der Datentabelle mit den Datensätze immer mit jedem Durchgang erhöhen
startWertVeraenderlich *= teilmenge;
//// Neuen Task erzeugen (alle diese Tasks verwenden die identische Methode), den Task der Taskliste zufügen
Task task = new Task(() => { DetektiereVerwaisteDateien(Lst_DateienInVereichnis.ToList<ProgInfos>(), tblDatensaetze.AsEnumerable().Skip(startWertVeraenderlich).Take(teilmenge).CopyToDataTable()); });
lstTasksZuLoeschendeDatensaetze.Add(task);
}
//// Nun jeden Task starten
foreach (Task tsk in lstTasksZuLoeschendeDatensaetze)
{
tsk.Start();
}
//// Dann warten bis "alle !!!" Tasks fertig sind
try
{
Task.WaitAll(lstTasksZuLoeschendeDatensaetze.ToArray(), new TimeSpan(0, 0, 60));
}
catch (AggregateException agEx)
{
}
catch (OperationCanceledException agEx)
{
}
catch (Exception ex)
{
}
}
/* ========================================================================================================================================================================*/
/// <summary>
/// Entfernt die Datensaetze welche auch in der List<> existieren, alle übrigen Datensätze müssen somit noch gesondert verarbeitet werden
/// </summary>
/// <param name="lstDateien">List mit den Dateien im Verzeichnis</param>
/// <param name="tblDatensatzMengenbereich">Teilmenge der Datentabelle aufgeteilt nach Prozessorkernen</param>
private void DetektiereVerwaisteDateien(List<ProgInfos> lstDateien, DataTable tblDatensatzMengenbereich)
{
for (int datSatzNr = 0; datSatzNr < tblDatensatzMengenbereich.Rows.Count; datSatzNr++)
{
if (lstDateien.Contains(tblDatensatzMengenbereich.Rows[datSatzNr]["DateinameMitPfad"].ToString()))
{
datSatzNr--;
tblDatensatzMengenbereich.Rows.RemoveAt(datSatzNr);
}
}
}
/* ======================
Das Problem sind vermutlich eher ungünstige Datenstrukturen.
DataTable sollte man nicht benutzen, wenn es nicht zwingend nötig ist.
Das Contains in DetektiereVerwaisteDateien auf der List ist maximal unperformant, da er die gesamte Liste mühselig für jeden Datensatz durchsuchen muss.
Vermutlich geht hier die meiste Zeit für deine Verarbeitung verloren.
Nimm hier am besten ein HashSet und mach darauf dein Contains.
Vermutlich kannst du damit schon deinen Ansatz zum parallelen Verarbeiten mit Tasks wieder einstampfen 😃
T-Virus
Developer, Developer, Developer, Developer....
99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.
Das Problem ist das in der List Dateiinfos enthalten sind die ich mit den Datensätzen der Datentabelle vergleichen muss. Mit dem Hashset habe ich noch nie gearbeitet, wenn das funktionieren sollte?
Aber das Prinzip der Vorgehensweise wäre richtig?
Es ist das Problem das je nachdem in den einen oder anderen Task ein Fehler geworgen wird. Ich habe mal den Tasks die Prozessorkernnummer der Schleife mitgegeben und gebe due in der Konsole aus, die ist in allen Tasks gleich, das ist seltsam.
Aber das Prinzip der Vorgehensweise wäre richtig?
Da du Paralelität erreicenh möchtest brauchst du ansich nicht selber mit Task Arbeiten, sondern kannst das von der Parallel-Klasse machen lassen. Du könntest dir dafür die Microsoft Dokumentation anschauen.
die ist in allen Tasks gleich, das ist seltsam.
Ansich ist das nichts seltsames, da du nirgends angibtst auf welches Kern die Task ausgeführt werden soll und die CLR oder CPU das verwalten. Meines Wissens nach kann man den Kern, aber auch nirgens angeben.
@oehrle
Vergiss erstmal den Ansatz mit den Tasks und schau dir die Doku von HashSet<T> und desen Contains an.
Die Tasks verschleiern m.M.n. nur das Problem, dass du mit dem Contains auf deine List<T> hast.
Wie geschrieben, verbrennst du damit die meiste Laufzeit, da du für jede Zeile im DataTable auch alle Einträge in der Liste bis zum ersten Treffer oder ggf. bis zum Ende durchsuchen musst.
HashSet ermittelt für die Einträge dann einen eindeutigen HashCode beim hinzufügen.
Dadurch können Treffer schnell gefunden werden ohne alle Einträge einzeln zu prüfen.
Links:
https://learn.microsoft.com/de-de/dotnet/api/system.collections.generic.hashset-1?view=net-8.0
https://learn.microsoft.com/de-de/dotnet/api/system.collections.generic.hashset-1.contains?view=net-8.0
Am Ende musst du nur durch die Rows im DataTable durchlaufen und gegen das HashSet prüfen ob es bereits einen Treffer enthält.
Wenn ja, kannst du die Zeile wie in dem Task Code entfernen.
Dazu brauchst du halt nur keine Tasks, die sind hier eher Kontraproduktiv und verschleiern das Problem mit der Liste nur.
T-Virus
Developer, Developer, Developer, Developer....
99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.
Ja, danke erst mal für eure Infos. Habe mir gerade das mal mit dem HashSet angesehen. Das werde ich als erstes mal implementieren und die Daten der List darin abbilden.
Jetzt noch zu dem anderen was ich noch äußern möchte, aber bitte nicht falsch verstehen, eventuell täusche ich mich da auch.
Also in der Vergangenheit hatte ich mich auch schon daran versucht, Verarbeitungen die längere Rechenzeit benötigen zu parallelisieren. Dazu gibt es ja die TaskParallel, mir Parallel.Foreach() usw ...
Ich hatte solche dinge auch schon ausprobiert, jetzt kommt das aaabbbeeer von mir ...
Ich habe z.B. eine Anwendung, mit der werden Programmdateien verarbeitet. Wenn ich zu Beginn nur 10000 Dateien untersuchen muss, aber nach 10 Monaten sind es 300000 Dateien, und die ganze Abarbeitung war bisher sequentiell in einer Methode, dann brauch ich für die 300000 Dateien mittlerweile eine immense Zeit für die Verarbeitung, und was mit dabei auffällt, wenn man mit Parallel.Foreach() arbeitet, dass ja zwischendurch Objekte gesperrt sind, wegen lock(), denn da wird kurz auf eine Datenmenge zugegriffen und damit ist diese kurz gesperrt. Deshalb wäre meine Idee mit dem Aufteilen der Daten, somit gäbe es keinen lock() und jeder Task arbeitet für sich, keine Sperrung wegen DataRace ... das war meine Idee hinter dieser ganzen Sache. Habe ich das verständlich für euch erklären können? Versteht ihr nun was ich meine? Wäre meine Idee in diesem Fall schneller als Parallel.Foreach() ??
Danke, ich teste mal ....
@oehrle
Durch Parallel.ForEach hast du halt auch Tasks, die auf die gleichen Datenstrukturen parallel zugreifen.
Für den Fall gibt es die Collection aus System.Collections.Generic.Concurrent, die sich um das Locking intern selbst kümmern.
Ich würde aber erstmal auf diesen ganzen Aufwand verzichten und alle anderen Möglichkeiten ohne Tasks ausloten.
Aktuell würde ich halt davon ausgehen, was ich hier nur anhand des Code sehen kann, dass deine Datenstrukturen nicht optimal sind/waren was unnötig Rechenzeit kostet und mit der Menge der Daten auch immer schlechter läuft.
Und hier kommt HashSet ins Spiel um solche Probleme optimal zu lösen.
Ich denke mit dem Ansatz mit HashSet kannst du dein Problem optimal lösen.
Meld dich einfach, falls es noch wo klemmt.
T-Virus
Developer, Developer, Developer, Developer....
99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.
Hi T-Virus, es hat geklappt. Das macht wirklich einiges aus an Zeit, da brauche ich an die eigentliche Frage erst gar nicht denken.
Vielen Dank
Freut mich zu hören, dass es dein Problem besser gelöst hat 😃
Und danke für das Feedback.
T-Virus
Developer, Developer, Developer, Developer....
99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.