![]() |
OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Container?
Hallo zusammen,
ich habe ein Programm was Lied-Dateien einliest. Das Programm funktioniert. Aber inzwischen ist mir die Wartezeit beim Einlesen der Dateieigenschaften einfach zu lang. Am Anfang habe ich mich nach reiflicher Überlegung bewusst *gegen* eine Datenbank entschieden! Somit war klar, dass ich beim Starten des Programms die Dateien immer neu einlesen muss. Das einlesen der Dateien über FindFirst() dauert bei ca. 15.000 Dateien akzeptable 1,7xxx Sekunden (mit Einfügen in ein Grid). Allerdings dauert das Auslesen der Dateieigenschaften wie z.B. der MP3-Tags weitere 1:50 Minuten. An dem Auslesen der Dateieigenschaften kann ich nichts mehr optimieren. Nun war meine Überlegung das Einlesen der Dateieigenschaften zu parallelisieren. Da ich noch Delphi XE nutze bin ich auf die OmniThreadLibrary gestoßen. Nun meine Fragen (als absoluter Neuling in diesem Bereich): 1. Welche Funktionsart aus OmniThreadLibrary wäre für meine Anforderung richtig? So wie ich es verstanden habe könnte es Pipelines sein, bin mir aber nicht sicher. 2. Welchen (Threadsaved) Container soll ich für das Zwischenspeichern der Dateieigenschaften verwenden? Meine momentane Vorstellung ist, dass ich die Dateieigenschaften temporär in ein Container (ObjectList?) einlese. 3. Nach dem parallelen einlesen der Dateieigenschaften plane ich den Inhalt des "Containers" an ein Grid (cxGridTableView von DevExpress) weiterzugeben. Sind diese Überlegungen richtig/sinnvoll? Ich danke schon mal im Voraus für eure Hilfe, Anregungen und Schubse in die richtige Richtung! |
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
Die Anzahl der Dateien ist fast irrelevant, schlimmer ist die Anzahl der Verzeichnisse, welche so gelesen wird. (also in Bezug auf die Dateisuche > FindFirst/FindNext)
Und dann nervt erst die Anzahl der Dateien, aber da du dort ja eigentlich kein großes Problem hast ... :angle: Aber multithread kannst du beim Zugriff auf dein VCL-Grid vergessen. Multithread auf einer HDD suchen verbessert auch nicht immer alles ... parallele Zugriffe können schnell mal alles extrem ausbremsen. (bei SSD sieht es anders aus) Nicht alle Daten im Grid anzeigen/laden, sondern nur intern speichern, in einer Liste oder Tree, oder ein schnelleres Grid verwenden (VirtualStringTree). Du kannst auch weiterhin beim Start suchen und zur Laufzeit eine DB oder ein MemoryDataSet mit den Daten füllen und in einem DB-Grid anzeigen. ![]() |
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
Oder, etwas verrücktere Idee: Zur Visualisierung statt eines Grids einen VirtualTreeView verwenden und nur die Detaildaten der Dateien auslesen, die sich tatsächlich im Viewport befinden. Ob ein solches Vorgehen möglich ist, hängt natürlich stark von der jeweiligen Anwendung ab.
|
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
Hallo,
mir ging es wirklich ums parallelisieren beim Einlesen der Dateieigenschaften. Das Grid ist in 1-2 Sek. mit allen Daten gefüllt. Nur beim parallelisieren des Ausleseprozesses der Dateieigenschaften sehe ich eine Möglichkeit die Wartezeit *spürbar* zu verringern. Doch worin sollte ich die Dateieigenschaften zwischenspeichern um diese nach dem parallelen Einlesen dem Grid zuzuordnen? Sollte ich ein Record verwenden, eine Klasse oder Objectlist oder eine generische Liste oder was gibt's noch? Das Ganze müsste halt Threadsaved sein. |
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
Zitat:
Zitat:
Beim Datei-Explorer sieht man das sehr schön. Wenn man einen Ordner mit vielen Bildern hat und den Explorer auf Miniansichten stellt. Dann sieht man zunächst erstmal nur das Datei-Icon (anhand der Metadaten ausgewählt) und nachdem der Reader-Thread drüber marschiert ist, wird die Darstellung im Listview aktualisiert mit einem gerenderten Thumbnail (was wohl noch vom Reader-Thread erledigt wurde). |
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
@Codehunter ,
erst mal Danke für deine Tipps. Ums visualisieren geht's mir gar nicht. Letztendlich geht es ums suchen von Interpreten, Titeln usw. um sich aus der gefilterten Menge schnell Playlisten zu erstellen. Dazu muss ich eben alle Dateinamen, Dateipfade und Dateidatum einlesen. Das läuft schon über einen Thread und ist nicht das Problem. Das Einlesen der Metadaten dauert dann aber zu lange. Diesen Prozess möchte ich beschleunigen durch Parallelisierung. Nur wohin lese ich die Metadaten ein? Du nanntest hier eine TList. Wenn ich z.B. Interpret, Titel, Album, Bewertung Lyrics, Kommentar, Track#, Disk#, Dateidatum usw. pro Datei habe, sehe ich das Problem der Datentrennung. Wie soll ich die Metadaten einer Datei in einer TList speichern? Semikolon-getrennt? Dann ist das Einlesen der Daten in das Grid aufwendig. Schön wäre eigentlich eine InMemory-Table wie z.B. ClientDataSet oder dxMemTable von DevExpress. Die sind aber leider nicht Thread safe. Ein Record wäre auch eine Idee. Da finde ich aber nichts in meiner Delphi Hilfe ob der Thread safed ist. :gruebel: |
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
Zunächst einmal sei
![]() Prinzipiell ist dein Anwendungsfall einer der ganz einfachen Fälle, wo es kaum konkurrierende Zugriffe gibt. Leite dir einfach eine eigene Klasse von TThread ab, die du initial mit dem Pfad zur Zieldatei startest. Das Auslesen der Metadaten aus der Datei und dessen Zwischenspeicherung passiert ausschließlich innerhalb des Thread-Objektes. Da kannst du dich fast 1-zu-1 an ![]() Ein ungefähres Grundgerüst:
Delphi-Quellcode:
Ich habs in Notepad++ geschrieben, ungetestet! Daher nicht wundern wenns irgendwo kleinere Tippfehler gibt. Soll ja auch nur ein Denkanstoß sein. Das Dictionary-Object DL enthält am Ende eine Liste von Records, welche sich über den Dateinamen als Schlüssel abfragen lassen. Weil die vom Thread ausgelesenen Metadaten auch nur im Thread selbst existieren, brauchst du dir um Threadsafe an der Stelle keine Gedanken machen. Das eigentliche Auslesen der Metadaten verwendet dann evtl. wieder Routinen, wo du darauf achten musst. Aber das soll ein anderes Thema sein.
type
Pmp3Data = ^Tmp3Data; Tmp3Data = record Interpret: string; Titel: string; Album: string; Bewertung: string; Lyrics: string; Kommentar: string; Track: Integer; Disk: Integer; Dateidatum: TDateTime; end; Tmp3DataList = TDictionary<string, Tmp3Data>; Tmp3ReaderThread = class; Tmp3ReaderComplete = procedure (Thread: Tmp3ReaderThread; FileName: string) of Object; Tmp3ReaderThread = class(TThread) private FData: Tmp3Data; FFileName: string; FOnComplete: Tmp3ReaderComplete; protected procedure DoComplete; procedure Execute; override; public property Data: Tmp3Data read FData; property FileName: string read FFileName write FFileName; property OnComplete: Tmp3ReaderComplete read FOnComplete write FOnComplete; end; Tmp3ThreadList = TList<Tmp3ReaderThread>; TForm1 = class(TForm) procedure Form1Create(Sender: TObject); procedure Form1Destroy(Sender: TObject); private DL: Tmp3DataList; TL: Tmp3ThreadList; FNumCompleteThreads: Integer; procedure AuslesenFertig(Thread: Tmp3ReaderThread; FileName: string); end; const // Wie viele Threads sinnvoll sind, hängt von der CPU ab, also wie viele // Cores, ob Hyperthreading verfügbar ist oder nicht usw. Am besten // konfigurierbar machen. MAX_READER_THREADS_SAME_TIME: Integer = 8; implementation procedure TForm1.Form1Create(Sender: TObject); begin DL:= Tmp3DataList.Create; TL:= Tmp3ThreadList.Create(TRUE); end; procedure TForm1.Form1Destroy(Sender: TObject); begin FreeAndNil(DL); // !!! Achtung! Du musst die Records auch noch vorher mit Dispose freigeben !!! FreeAndNil(TL); end; procedure TForm1.Button1Click(Sender: TObject); var Data: Pmp3Data; I, iFilesCount: Integer; sFile: string; SL: TStringList; T: Tmp3ReaderThread; begin // ... Liste_alle_relevanten_Dateien_inkl_Pfad(SL); iFilesCount:= SL.Count; DL.Clear; // !!! Achtung! Du musst die Records auch noch vorher mit Dispose freigeben !!! TL.Clear; // Evtl. noch laufende frühere Threads werden abgebrochen FNumCompleteThreads:= 0; for I:= 0 to iFilesCount - 1 do begin sFile:= SL[I]; New(Data); // Speicherplatz reservieren DL.Add(sFile, Data^); T:= Tmp3ReaderThread.Create(TRUE); T.FileName:= sFile; T.OnComplete:= AuslesenFertig; TL.Add(T); end; // Nun hast du eine Liste mit suspendierten Threads und eine Liste mit // reserviertem Platz für die Lese-Ergebnisse. Nun musst du nur noch dafür // sorgen, dass eine sinnvolle Anzahl Threads gleichzeitig läuft, z.B. acht. I:= 0; repeat T:= TL[I]; T.Start; Inc(I); until (I = TL.Count) or (I = MAX_READER_THREADS_SAME_TIME); end; procedure TForm1.AuslesenFertig(Thread: Tmp3ReaderThread; FileName: string); var Data: Pmp3Data; I: Integer; T: Tmp3ReaderThread; begin Inc(FNumCompleteThreads); if DL.TryGetValue(FileName, Data) then begin // reservierten Speicher im Hauptthread mit den Daten aus dem Reader-Thread // füllen Data^.Interpret = Thread.Data.Interpret; // usw. ... I:= 0; repeat // Threadliste nach dem nächsten noch nicht abgearbeiteten Thread // durchsuchen und den Thread starten. T:= TL[I]; if not T.Finished then begin T.Start; Break; end; Inc(I); until I = TL.Count; end; end; procedure Tmp3ReaderThread.Execute; begin Lese_die_Datei_aus(FFileName, FData); // Speicher von FData liegt im Thread! DoComplete; end; procedure Tmp3ReaderThread.DoComplete; begin if Assigned(FOnComplete) then begin Synchronize(FOnComplete(Self, FFileName)); end; end; Anstelle der Records kannst du auch eine Klasse bauen. Wie du die Auslese-Ergebnisse verwaltest ist eigentlich dir überlassen. Ich verwende gerne Records, weil ich den Speicher vorher in der benötigten Menge reservieren kann. Klassen haben dann wieder den Vorteil, dass sich der Delphi-Speichermanager um das saubere Freigeben kümmert. Nachdem das Auslesen fertig ist, wird
Delphi-Quellcode:
aufgerufen, welche den EventHandler
DoComplete
Delphi-Quellcode:
im Hauptthread synchronisiert aufruft. Dort werden dann die ausgelesenen Daten aus dem Thread-Objekt in den Hauptthread übernommen.
AuslesenFertig
Der Beispielcode ist absichtlich nicht komplett. Soll nur verdeutlichen, wie du den Speicher so verwalten kannst dass es bei der Threadverarbeitung nicht kracht. Die suspendierten Threads musst du in sinnvollen Häppchen starten. Die Anzahl ist von der Maschine abhängig und sollte nutzerkonfigurierbar sein. Über FNumCompleteThreads hast du jederzeit im Hauptthread die Anzahl der bereits fertigen Threads, über TL.Count die Anzahl aller Threads inkl. der suspendierten und fertigen. Daraus kannst du ggf. noch eine Progressbar ansteuern. Aber WICHTIG: Diese Progressbar AUSSCHLIESSLICH über die Prozedur
Delphi-Quellcode:
aktualisieren. Sonst gibts wieder Speicherkuddelmuddel.
AuslesenFertig
|
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
Hi Leute,
wenn ich es richtig sehe geht es ihm nicht darum wie man das parallelisiert sondern, worin er das speichert. Zitat:
|
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
Hmm..
Zitat:
je File ein Tmp3Data, gespeichert in DL: Tmp3DataList; als Liste... ;) Wenn alle Threads fertig sind, kann auf DL zugegriffen werden (z.B. in AuslesenFertig) und die Metadaten aus der Liste als z.B. Tabelle angezeigt werden. ;) |
AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai
Hallo Codehunter,
erst einmal herzlichen Dank für deine ausführliche Unterstützung!! :thumb: Ich glaube (:?) den groben Ansatz habe ich sogar verstanden. Da hab ich an den nächsten Schlecht-Wetter-Wochenenden einiges um mich einzuarbeiten. Da die Art und Weise deiner Programmierung für mich neue Materie ist, werde ich wohl noch Nachfragen haben. :oops: Also nochmals vielen Dank und einen schönen Tag noch! |
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:28 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz