Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi Thumbnails via Threads laden (https://www.delphipraxis.net/116956-thumbnails-via-threads-laden.html)

Nils_13 9. Jul 2008 10:05


Thumbnails via Threads laden
 
Hi,

ich möchte viele Thumbnail-Ladevorgänge in Threads auslagern. Wie könnte man das performant tun ? Ich dachte eine Zeit lang an eine Rekursion. Ein Thread arbeitet, wenn fertig wird der nächste erstellt, welcher wieder arbeitet. Dies würde so lange ablaufen, bis nichts mehr übrig ist. Es zu programmieren erschien mir allerdings unmöglich. Daher dachte ich an mehrere Threads welche mit Critical Sections gesichert sind. So wie ich das sehe bräuchte ich dann ein Array of TThumbnailThread (TThumbnailThread ist eine Ableitung von TThread). Brauche ich das wirklich ? Denn ich denke das wäre ab einer gewissen Anzahl von zu ladenden Thumbnails sehr belastend. Gibt es noch andere Lösungsmöglichkeiten ?

Apollonius 9. Jul 2008 10:53

Re: Thumbnails via Threads laden
 
Du solltest nicht viel mehr Threads erstellen als CPU-Kerne vorhanden sind (wobei Hyperthreading-Kerne mehrfach gezählt werden sollten, aber das ist nicht so wichtig). Sonst gibt es nämlich nur mehr Overhead.
Du könntest das Ganze gut mit einer Auftragsliste implementieren: Jedes zu bearbeitende Bild ist ein Auftrag. Wenn du die noch ausstehenden und beendeten Aufträge in einer Linked List speicherst, kommst du sogar ohne Critical Sections aus.

Nils_13 9. Jul 2008 11:11

Re: Thumbnails via Threads laden
 
Wäre es dann nicht schwierig die geladenen Bilder zurückzugeben ? Es soll nämlich nach jedem geladenen Bild dieses auch in der ListView erscheinen.

Apollonius 9. Jul 2008 11:28

Re: Thumbnails via Threads laden
 
Das sollte kein riesiges Problem darstellen. TThread bietet die Möglichkeit, dem Hauptthread Arbeiten zuzuteilen, und zwar nicht blockierend wie bei Synchronize (Klassenmethode Queue). Da könntest du dann einfach immer ein Aktualisierungsroutine angeben. Am besten führst du einfach eine globale boolsche Variable ein: Sie hat den Status False, wenn die Routine gerade nicht in der Warteschlange steckt, ansonsten True. Am Anfang der Aktualisierungsmethode setzt du sie auf false und holst so viele Thumbnails wie möglich aus deiner Liste der fertigen Aufträge. Wenn ein Thread einen Auftrag bearbeitet hat, setzt er den Status der globalen Variable mit InterlockedExchange auf True und fügt, falls die Variable vorher den Wert False hatte, die Aktualisierungsroutine der Warteschlange hinzu.

Um das deutlich zu machen:
Delphi-Quellcode:
var GlobalFlag: Boolean;

procedure ThreadRoutine;
var Job: TThumbnailJob;
begin
  while DequeueJob(JobsToDo, Job) do
  begin
    Job.Execute;
    QueueJob(ExecutedJobs, Job);
    if InterlockedExchange(Integer(GlobalFlag), Integer(True)) = Integer(False) then
      TThread.Queue(nil, MyForm.UpdateListView);
  end;
end;

procedure TMyForm.UpdateListView;
var Job: TThumbnailJob;
begin
  GlobalFlag := false;
  while DequeueJob(ExecutedJobs, Job) do
  begin
    AddThumbnailToListView(Job);
    Job.Free;
  end;
end;

Nils_13 9. Jul 2008 11:59

Re: Thumbnails via Threads laden
 
Wäre dann JobsToDo eine LinkedList ?

Apollonius 9. Jul 2008 12:02

Re: Thumbnails via Threads laden
 
Das wäre das Effektivste. Du kannst natürlich auch eine TObjectQueue mit einer Critical Section oder was auch immer verwenden, aber der große Vorteil von verketteten Listen ist, dass du kein Synchronisations-Objekt benötigst. Allerdings ist die Implementierung nicht ganz trivial, du brauchst die Interlocked-Funktionen. Windows bietet aber entsprechende Funktionen an; es handelt sich zwar um einen Stack und nicht um eine Queue, aber das sollte eigentlich egal sein.

Nils_13 9. Jul 2008 12:13

Re: Thumbnails via Threads laden
 
Ich frage mich gerade ganz ehrlich, was überhaupt wirklich eine LinkedList ist. Stehen nicht in der Liste dann die ganzen Thumbnails drinnen ? Damit es funktioniert muss ich allerdings einen eigenen Datentyp übergeben, weil sonst der Thread zu wenig Informationen besitzt um zu arbeiten. Dafür kann ich eigentlich nur ein Array of TDatentyp nehmen. Abgesehen davon gilt noch zu beachten, dass es nicht zu sehr auf der API basieren sollte. Daher habe ich extra TThread und nicht BeginThread genommen.

Apollonius 9. Jul 2008 12:14

Re: Thumbnails via Threads laden
 
TThumbnailJob ist eine Klasse mit allen benötigten Informationen. Wenn du die API nicht verwenden willst und du nichts gegen ein kleines bisschen Overhead hast, kannst du auch einfach eine TThreadList nehmen.

Nils_13 9. Jul 2008 12:54

Re: Thumbnails via Threads laden
 
Und in diese Liste kommen alle noch abzuarbeitenden Bilder rein ?

Apollonius 9. Jul 2008 16:24

Re: Thumbnails via Threads laden
 
Ja. Du hast zwei Listen, eine für die fertigen Aufträge und eine für die noch ausstehenden. Jeder Auftrag wird durch eine Objektinstanz repräsentiert, die die Bilder, eventuell andere Einstellungen und das fertige Thumbnail (das ist natürlich nur in der Liste der fertigen Aufträge zugewiesen) enthält.

alzaimar 9. Jul 2008 16:53

Re: Thumbnails via Threads laden
 
Ich habe hier irgendwo eine 'WorkerThreadPool' Klasse geschrieben, die Dir eigentlich die Arbeit abnimmt. Du musst nur noch den Job definieren und dann gehts los. Such mal danach, ich glaube, sie funktioniert. :zwinker:
Zitat:

Zitat von Apollonius
Du solltest nicht viel mehr Threads erstellen als CPU-Kerne vorhanden sind

Das würde ich nicht so pauschal aussagen. Nehmen wir an, Dein Thread soll Daten laden, wobei die Gegenseite der Bottleneck ist, also ziemlich langsam. Jeder Thread verbindet sich mit einer anderen (lahmen) Gegenstelle. Wenn Du nun 10 Threads am Laufen hast, warten die alle und die CPU langweilt sich immer noch; Du könntest also sehr viele Threads erzeugen, die dann gleichzeitig (ziemlich langsam) Daten einlesen, ohne die CPU überhaupt großartig zu belasten.

Ein konkretes Beispiel ist ein Portscanner: Ich erstelle sehr viele Threads, die parallel jeweils einen Port an der Gegenstelle abfragen, dabei müssen sie Timeout-Intervalle beachten. Jeder Thread wartet also z.B. 10 Sekunden auf einen Timeout. Damit ich nun nicht 65.000 * 10 Sekunden Warten muss, was doch eine ganze Weile ist, kann ich einfach jeweils 100 Threads parallel laufen lassen und warten dann nur knapp ein 1/100 der Zeit (erschlag mich nicht, wenn die 10 Sekunden Timeout zu kurz sind).

Ein weiteres Beispiel ist ein TCP-Server, der für jede einkommende Verbindung einen eigenen Thread aufmacht. Wieso sollte das auf 1-8 Threads beschränkt sein? Viel zu tun hat so ein TCP-Thread i.a. ja nicht.

Bei Dir wäre der Bottleneck vermutlich die Festplatte. So ein Thread wartet eine kurze Zeit, bis das Bild geladen ist. In der Zeit kann ein anderer Thread bereits mit der Berechnung des Thumbnails anfangen usw. Bevor Du jedoch Threads basteltst, würde ich vielleicht die Thumbnailerstellung optimieren (wenn da was zu holen ist).

Apollonius 9. Jul 2008 16:58

Re: Thumbnails via Threads laden
 
Ja, wenn die Threads warten, ist das klar. Ich ging allerdings davon aus, dass die Bilder bereits im Speicher vorliegen und nur noch ein Verkleinerungsalgorithmus durchgeführt werden muss. Andernfalls hast du natürlich recht, Alzaimar.

Nils_13 9. Jul 2008 17:04

Re: Thumbnails via Threads laden
 
Ich finde zu WorkerThreadPool nichts. Ich habe die Bilder nicht im Speicher liegen. Denn das kostet schließlich am meisten Zeit. Beim Laden und Generieren lässt sich nichts mehr verbessern denke ich. Was wäre also am sinnvollsten ? Ich habe außerdem folgenden leicht fehlerhaften Code geschrieben, welcher wenigstens mal etwas funktioniert. Scheinen aber wirklich Fehler drinnen zu sein, die ich gerade nicht finde - gewisse Bilder sind einfach schwarz, wechselt man das Verzeichnis (ist ein Dateiverwaltungsprogramm) werden die Bilder nicht neu geladen usw. Scheint aber am ThumbnailDone zu liegen. Ich habe das Gefühl, es sei allgemein aber noch etwas instabil.
Delphi-Quellcode:
type
  TDone = procedure (Items : TDynItemArray) of object;
  TThumbnailThread = class(TThread)
  private
    fDone : TDone;
    procedure SyncDone;
  protected
    procedure Execute; override;
  public
    Items : TDynItemArray;
    Img  : TImageList;
    property Done : TDone read fDone write fDone;
  end;

procedure TThumbnailThread.Execute;
var i      : Integer;
    Picture : TPicture;
    Bmp    : TBitmap;
begin
  try
    for i := 0 to High(Items) do
    begin
      try
        Picture := TPicture.Create;
        Picture.LoadFromFile(Items[i].Pfad+Items[i].Name);
        Bmp := TBitmap.Create;
        with Bmp do
        begin
          ...
          Img.Add(Bmp, nil);
          Items[i].ImageIndex := Pred(Img.Count);
          Free;
        end;
      except

      end;
      SyncDone;
    end;
  except
    on e: exception do
    begin

    end;
  end;
end;

procedure TThumbnailThread.SyncDone;
begin
  if Assigned(fDone) then
    fDone(Items);
end;

procedure TRazFileManager.LoadThumbnails;
var Thread : TThumbnailThread;
begin
  Thread := TThumbnailThread.Create(True);
  with Thread do
  begin
    Items := Thumbnails;
    Img  := Img32;
    Done := ThumbnailDone;

    Resume;
  end;
end;

procedure TRazFileManager.ThumbnailDone(Items : TDynItemArray);
var i : Integer;
begin
  for i := 0 to High(Items) do
    DirsFiles[DirCount+Items[i].Index].ImageIndex := Items[i].ImageIndex;          
end;
Edit: habe einen wichtigen Fehler gefunden und beseitigt. Nun scheint es ganz gut zu laufen.

Die Muhkuh 9. Jul 2008 17:35

Re: Thumbnails via Threads laden
 
Zitat:

Zitat von Nils_13
Ich finde zu WorkerThreadPool nichts.

:arrow: http://www.delphipraxis.net/internal...t.php?t=111982

alzaimar 10. Jul 2008 09:05

Re: Thumbnails via Threads laden
 
Deine Imageliste ist nicht thread-safe. Wenn zwei threads gleichzeitig darauf zugreifen, gibts Probleme. Du musst den Zugriff mit TCriticalSections synchronisieren. Weiterhin ist die TImageList eine VCL-Komponente und diese Komponenten dürfen nur im Hauptthread verwendet werden (z.B. mit Synchronize).

Ich würde an Deiner Stelle erst die Thumbnailerstellung 100% richtig hinbekommen und erst dann mal mit Threads rumexperimentieren. Zu viel auf einmal macht einen nur konfus, und man weiss gar nicht, wo man einen Fehler suchen soll.


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:24 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz