AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Algorithmen, Datenstrukturen und Klassendesign Delphi OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Container?
Thema durchsuchen
Ansicht
Themen-Optionen

OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Container?

Ein Thema von juergen · begonnen am 15. Okt 2017 · letzter Beitrag vom 27. Okt 2017
Antwort Antwort
Benutzerbild von juergen
juergen

Registriert seit: 10. Jan 2005
Ort: Bönen
1.176 Beiträge
 
Delphi 11 Alexandria
 
#1

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 16. Okt 2017, 18:47
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.
Jürgen
Indes sie forschten, röntgten, filmten, funkten, entstand von selbst die köstlichste Erfindung: der Umweg als die kürzeste Verbindung zwischen zwei Punkten. (Erich Kästner)
  Mit Zitat antworten Zitat
Benutzerbild von Codehunter
Codehunter

Registriert seit: 3. Jun 2003
Ort: Thüringen
2.284 Beiträge
 
Delphi 12 Athens
 
#2

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 17. Okt 2017, 07:16
Das Grid ist in 1-2 Sek. mit allen Daten gefüllt.
Darum ging es mir eigentlich gar nicht. Vielmehr als Denkanstoß, ob du die zig Tausend Dateien NUR einliest um sie AUSSCHLIESSLICH zu visualisieren. Oder tust du noch andere Dinge mit dem erhobenen Datenbestand? Falls es dir nur um Visualisierung geht, was ich aus der Aussage ableite dass du keine Datenbank verwenden willst, dann könntest du mit dem Virtualtree eine Menge Rechenzeit einsparen und ggf. ganz auf Multithreading verzichten.

Das Ganze müsste halt Threadsaved sein.
Wenn ich den Ablauf richtig verstanden habe, dann kennst du die genaue Anzahl von einzulesenden Dateien ja schon vorher. In dem Fall kannst du dir doch eine entsprechende Anzahl Records erstellen, welche die einzulesenden Daten aufnehmen, diese in eine TList packen und dem jeweiligen Thread nur einen Pointer auf den jeweiligen Record mitgeben. Synchronisieren müsstest du nur die Teile, die während des Einlesens schon benötigt werden. Der Rest liegt einfach erstmal als reservierter Speicher vor und wird nach und nach gefüllt.

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).
Ich mache grundsätzlich keine Screenshots. Schießen auf Bildschirme gibt nämlich hässliche Pixelfehler und schadet der Gesundheit vom Kollegen gegenüber. I und E zu vertauschen hätte den selben negativen Effekt, würde aber eher dem Betriebsklima schaden
  Mit Zitat antworten Zitat
Benutzerbild von juergen
juergen

Registriert seit: 10. Jan 2005
Ort: Bönen
1.176 Beiträge
 
Delphi 11 Alexandria
 
#3

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 17. Okt 2017, 22:26
@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.
Jürgen
Indes sie forschten, röntgten, filmten, funkten, entstand von selbst die köstlichste Erfindung: der Umweg als die kürzeste Verbindung zwischen zwei Punkten. (Erich Kästner)
  Mit Zitat antworten Zitat
Benutzerbild von Codehunter
Codehunter

Registriert seit: 3. Jun 2003
Ort: Thüringen
2.284 Beiträge
 
Delphi 12 Athens
 
#4

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 19. Okt 2017, 11:02
Zunächst einmal sei das hier als Einstiegslektüre empfohlen.

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 dieses Beispiel halten. Wenn dein Thread mit seiner Arbeit fertig ist, übergibt er die gewonnenen Daten an den Hauptthread. Dafür dann die zuvor erwähnte Speicherreservierung.

Ein ungefähres Grundgerüst:
Delphi-Quellcode:
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;
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.

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 DoComplete aufgerufen, welche den EventHandler AuslesenFertig im Hauptthread synchronisiert aufruft. Dort werden dann die ausgelesenen Daten aus dem Thread-Objekt in den Hauptthread übernommen.

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 AuslesenFertig aktualisieren. Sonst gibts wieder Speicherkuddelmuddel.
Ich mache grundsätzlich keine Screenshots. Schießen auf Bildschirme gibt nämlich hässliche Pixelfehler und schadet der Gesundheit vom Kollegen gegenüber. I und E zu vertauschen hätte den selben negativen Effekt, würde aber eher dem Betriebsklima schaden

Geändert von Codehunter (19. Okt 2017 um 11:09 Uhr)
  Mit Zitat antworten Zitat
freimatz

Registriert seit: 20. Mai 2010
1.495 Beiträge
 
Delphi 11 Alexandria
 
#5

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 19. Okt 2017, 14:07
Hi Leute,
wenn ich es richtig sehe geht es ihm nicht darum wie man das parallelisiert sondern, worin er das speichert.
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.
  Mit Zitat antworten Zitat
HolgerX

Registriert seit: 10. Apr 2006
Ort: Leverkusen
984 Beiträge
 
Delphi 6 Professional
 
#6

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 19. Okt 2017, 15:12
Hmm..

Hi Leute,
wenn ich es richtig sehe geht es ihm nicht darum wie man das parallelisiert sondern, worin er das speichert.
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.
Ist doch auch im Beispiel von Codehunter vorhanden:

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.
  Mit Zitat antworten Zitat
freimatz

Registriert seit: 20. Mai 2010
1.495 Beiträge
 
Delphi 11 Alexandria
 
#7

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 20. Okt 2017, 17:55
Ist doch auch im Beispiel von Codehunter vorhanden:

je File ein Tmp3Data, gespeichert in DL: Tmp3DataList; als Liste...
Ja das ist in dem Beispiel vorhanden. Ich sah das nur als ein Beispiel an um die Parallelierierung zu zeigen. Die konkreten Typen halte ich für diskussionswürdig und ich meinte darum ging es dem Threadersteller.

Nun konkret: Vorgeschlagen wurde in Record "Tmp3Data "für einen Eintrag. Das finde ich schon mal nicht schlecht.
Dann aber schreibst du "als Liste". Im Vorschlag steht aber "Tmp3DataList = TDictionary<string, Tmp3Data>;". Das ist keine Liste. TDictionary verwende ich selber häufig, juergen könnte dann Probleme haben alle Einträge da rauszuholen. Es kommt drauf an wie da zugriffen werden soll.
Später gibt es dann noch doch eine Liste "Tmp3ThreadList = TList<Tmp3ReaderThread>".
Ich möchte hier nicht dem Codehunter an den "Karren fahren", es sollte ja ein Denkanstoss sein und scheint auch geholfen zu haben.
  Mit Zitat antworten Zitat
Benutzerbild von juergen
juergen

Registriert seit: 10. Jan 2005
Ort: Bönen
1.176 Beiträge
 
Delphi 11 Alexandria
 
#8

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 19. Okt 2017, 15:20
Hallo Codehunter,

erst einmal herzlichen Dank für deine ausführliche Unterstützung!!
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.

Also nochmals vielen Dank und einen schönen Tag noch!
Jürgen
Indes sie forschten, röntgten, filmten, funkten, entstand von selbst die köstlichste Erfindung: der Umweg als die kürzeste Verbindung zwischen zwei Punkten. (Erich Kästner)
  Mit Zitat antworten Zitat
Benutzerbild von GPRSNerd
GPRSNerd

Registriert seit: 30. Dez 2004
Ort: Ruhrpott
239 Beiträge
 
Delphi 10.4 Sydney
 
#9

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 20. Okt 2017, 13:20
Hallo Codehunter,

ich habe interessiert mitgelesen, da ich ein ähnliches Problem mit dem Einlesen von Dateieigenschaften mit Parallelisierung lösen und beschleunigen möchte.
Ich habe deinen Code jetzt pro Forma mal in ein Testprogramm eingebaut und irgendwas stimmt mit der Parametrisierung des synchonize()-Aufrufs noch nicht:

[dcc32 Fehler] uMain.pas(247): E2250 Es gibt keine überladene Version von 'Synchronize', die man mit diesen Argumenten aufrufen kann

in der Codezeile:
    Synchronize(FOnComplete(Self, FFileName));

Hast du ne Idee? (dafür reicht mein Thread-Wissen nicht aus)
Stefan
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.075 Beiträge
 
Delphi 10.4 Sydney
 
#10

AW: OmniThreadLibrary: Was ist für meine Anforderung der richtige Weg? Welchen Contai

  Alt 20. Okt 2017, 15:03
[dcc32 Fehler] uMain.pas(247): E2250 Es gibt keine überladene Version von 'Synchronize', die man mit diesen Argumenten aufrufen kann

in der Codezeile:
    Synchronize(FOnComplete(Self, FFileName)); Hast du ne Idee? (dafür reicht mein Thread-Wissen nicht aus)

Wenn es in den Mainthread soll:
    Synchronize(nil, FOnComplete(Self, FFileName));

Wie machst du es sonst, wenn der Compiler nach dem Code eintippen meldet das in der Parameterliste ein Argument zuviel oder zuwenig ist?
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:31 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-2025 by Thomas Breitkreuz