![]() |
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 ? |
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. |
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.
|
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; |
Re: Thumbnails via Threads laden
Wäre dann JobsToDo eine LinkedList ?
|
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
![]() |
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.
|
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.
|
Re: Thumbnails via Threads laden
Und in diese Liste kommen alle noch abzuarbeitenden Bilder rein ?
|
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.
|
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:
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). |
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.
|
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:
Edit: habe einen wichtigen Fehler gefunden und beseitigt. Nun scheint es ganz gut zu laufen.
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; |
Re: Thumbnails via Threads laden
Zitat:
![]() |
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 13:08 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