![]() |
Rechenintensiven Thread aufräumen
Ich habe ein Scanprogramm, dass die Bilder sofort verarbeitet und dem Benutzer Vorschläge für die beste Komprimierung machen soll. Für die Berechnung werden Threads verwendet. Das Komprimieren der Bilder (derzeit PNG und JPEG) erledige ich über den Code/die Units von Delphi. Und weil die Bilder größer sind, kann es manchmal dauern.
Wenn der Benutzer das Programm beendet, klappt das Dank Threads, hinterlässt aber Speicherlecks. Das gefällt mir natürlich nicht. Der Code dazu sieht folgendermaßen aus:
Delphi-Quellcode:
Da der Code unter
Unit _CalcThread;
Interface Uses System.Classes, Vcl.Graphics, Vcl.Imaging.pngimage, Vcl.Imaging.jpeg; Type TCodecType = (cdPNG, cdJPEG); TOnCalcDone = Procedure(Codec: TCodecType; Size: Int64) Of Object; TCalcThread = Class(TThread) Private fSize : Int64; fCodec : TCodecType; fBitmap : TBitmap; fOnCalcDone : TOnCalcDone; Procedure EventOnCalcDone; Public Constructor Create(Codec: TCodecType; Bitmap: TBitmap; OnCalcDone: TOnCalcDone); Procedure Execute; Override; Destructor Destroy; Override; End; Implementation // Absicherung des Eriegnis-Aufrufs -------------------------------------------- Procedure TCalcThread.EventOnCalcDone; Begin If Assigned(fOnCalcDone) Then // Sicherheitsprüfung FOnCalcDone(fCodec, fSize); // Ereignis auslösen End; // Ein Thread wird erstellt ---------------------------------------------------- Constructor TCalcThread.Create(Codec: TCodecType; Bitmap: TBitmap; OnCalcDone: TOnCalcDone); Begin fSize:= -1; // ungültigen Wert vorgeben fCodec:= Codec; // Codec merken fOnCalcDone:=OnCalcDone; // Zeiger auf Ereignis merken fBitmap:=TBitmap.Create; // TBitmap anlegen fBitmap.Assign(Bitmap); // Bild kopieren FreeOnTerminate:=True; // Speicher selbst freigeben Inherited Create(True); // Thread erstellen Priority:= tpIdle; // geringe Priorität // Resume; // "Start" löst Exception aus! Start; // "Resume" ist veraltet End; // Der eigentliche Thread (=die Berechnung) ------------------------------------ Procedure TCalcThread.Execute; Var lMemStream : TMemoryStream; Begin lMemStream:=TMemoryStream.Create; // Nur im Speicher arbeiten Try Case fCodec Of cdPNG : With TPngImage.Create Do // temp. PNG-Objekt anlegen Try Filters:=[pfNone, pfSub, pfUp, pfAverage, pfPaeth]; CompressionLevel:=9; // Maximale Kompression Assign(fBitmap); // Bilddaten übernehmen SaveToStream(lMemStream); // virtuell abspeichern fSize:=lMemStream.Size; // Größe merken Finally Free; // PNG-Objekt freigeben End; cdJPEG: With TJPEGImage.Create Do // tenp. JPEG-Objekt anlegen Try CompressionQuality:=80; // 100 = max, 1 = min Assign(fBitmap); // Bilddaten übernehmen SaveToStream(lMemStream); // virtuell abspeichern fSize:=lMemStream.Size; // Größe merken Finally Free; // JPEG-Objekt freigeben End; End; Finally lMemStream.Free; // TMemoryStream freigeben Synchronize(EventOnCalcDone); // Das Ende mitteilen End; End; // Der Thread wird beendet ----------------------------------------------------- Destructor TCalcThread.Destroy; Begin fBitmap.Free; // TBitmap selbst freigeben fOnCalcDone:=nil; // Zeiger löschen Inherited; // den Rest ausführen End; End.
Delphi-Quellcode:
sehr kurz ist, macht es keinen Sinn auf
Execute
Delphi-Quellcode:
zu reagieren.
Terminated
Zusatzfrage: Der Compiler sowie die Hilfe sagen mir,
Delphi-Quellcode:
sei veraltet. Allerdings erzeugt
TThread.Resume;
Delphi-Quellcode:
bei mir immer eine Exception vom Typ
TThread.Start;
Delphi-Quellcode:
. Und was noch schlimmer ist: bevor ich die Message dazu erhalte, zeigt mir Windows 8.1 an "Das Programm ... funktioniert nicht mehr richtig. Online nach einer Lösung suchen...". Der Debugger stürzt ab, die exe-Datei lässt sich nicht mehr löschen und damit neu compilieren.
EInvalidPointer
Wie löse ich diese Probleme? Gruß, Alex |
AW: Rechenintensiven Thread aufräumen
Ich würde es bei den Speicherlecks lassen - Windows räumt ja auf. Aber wenn es dir so wichtig ist:
:arrow: Komprimiere nur einen kleinen Teil des Bilds und nehme dies als Anhaltspunkt, welches Format besser ist :arrow: Schreibe den JPG/PNG Algorithmus selbst oder binde eine Bibliothek ein, die das unterbrechen gestattet. :arrow: Entwickle eine Heuristik. Zum Beispiel sind Bilder mit Gradienten und homogenen Flächen tendenziell besser für PNG geeignet, Bilder mit vielen Details besser für JPG. |
AW: Rechenintensiven Thread aufräumen
Halt, warte: Du beendest das Programm während die Threads noch laufen und wunderst dich dann das es Speicherlecks gibt? Wenn du die Threads nicht "sauber" beendest, wird der Speicher nicht freigegeben.
![]() Eine etwas saubere Lösung wäre es, dir ein TStream-Delegate zu erstellen, mit dem du die Berechnung aus einem anderem Thread stoppen kannst (durch Exceptions und/oder fehlgeschlagene Writes). Was mich ein wenig wundert, ist dass du für die "Abschätzung" die eigentliche Berechnung ausführst und dann das Ergebnis wegschmeißt. Wenn der Benutzer sich dann entschieden hat, machst du die Kompression nochmal? Zitat:
|
AW: Rechenintensiven Thread aufräumen
Wie wäre es, wenn man auf das Ende der Threads wartet, bevor sich die Anwendung beendet? :stupid:
|
AW: Rechenintensiven Thread aufräumen
Zitat:
|
AW: Rechenintensiven Thread aufräumen
|
AW: Rechenintensiven Thread aufräumen
Zitat:
Zitat:
Außerdem könntest du im Threadpool vor dem Vergeben einer neuen Aufgabe prüfen, ob die Anwendung beendet wurde. An deiner Stelle würde ich mir tatsächlich eine TStream-Ableitung schreiben und mal gucken, wie die Bibliotheken den Stream benutzen. Wenn die regelmäßig Schreibzugriffe auf den Stream machen, dann wäre das eine schöne Gelegenheit, den ganzen Quark mit einer Exception (oÄ.) abzubrechen. |
AW: Rechenintensiven Thread aufräumen
Wenn ein Prozess beendet wird, egal wie, ob regulär oder wenn über den Taskmanager, gibt es keine Speicherlecks. Wo sollen die auch sein? Speicherlecks kann es nur innerhalb eines Prozesses geben. Dieser existiert aber nicht mehr. Man kann ja auch kein Loch in einem Loch buddeln. ;) Des weiteren schließt Windows automatisch alle von dem Prozess geöffneten Händels. Also: Prozess beendet, alles gut.
Natürlich kann man darüber diskutieren, ob es guter Programmierstil ist nicht hinter sich aufzuräumen. Da hat jeder seine eigene Meinung. |
AW: Rechenintensiven Thread aufräumen
Wenn man selber aufräumt, dann sieht man zumindestens, ob sich unbeabsichte Löcher verstecken.
Und wenn richtig aufgeräumt wird, dann sind Fehler beim Beenden minimiert. z.B. Komponente auf Form, die beim Beenden auf etwas Globales in einer Unit zugreift ... wenn die Unit bereits ordentlich entladen wurde, dann würde es durt womöglich schön knallen. |
AW: Rechenintensiven Thread aufräumen
Die Spiecherlecks werdn doch deshalb angezeigt, weil die Threads beim Beenden des Prozesses einfach abgeschossen werden und die von jedem Thread belegten Ressourcen nicht freigeben wurden.
Wenn man das nicht haben möchte, dann muss man sich eben selber um die Freigabe der Threads kümmern und nicht
Delphi-Quellcode:
wegdelegieren.
TThread.FreeOnTerminate := True;
BTW: Das hier ist grober Unfug:
Delphi-Quellcode:
Während der
Constructor TCalcThread.Create(Codec: TCodecType; Bitmap: TBitmap;
OnCalcDone: TOnCalcDone); Begin fSize:= -1; // ungültigen Wert vorgeben fCodec:= Codec; // Codec merken fOnCalcDone:=OnCalcDone; // Zeiger auf Ereignis merken fBitmap:=TBitmap.Create; // TBitmap anlegen fBitmap.Assign(Bitmap); // Bild kopieren FreeOnTerminate:=True; // Speicher selbst freigeben Inherited Create(True); // Thread erstellen Priority:= tpIdle; // geringe Priorität // Resume; // "Start" löst Exception aus! Start; // "Resume" ist veraltet End;
Delphi-Quellcode:
abgearbeitet wird wird der Thread nicht loslaufen! Mit der Erkenntnis kann der Code wie folgt geschreiben werden:
Constructor
Delphi-Quellcode:
Der
Constructor TCalcThread.Create(Codec: TCodecType; Bitmap: TBitmap;
OnCalcDone: TOnCalcDone); Begin fSize := -1; // ungültigen Wert vorgeben fCodec := Codec; // Codec merken fOnCalcDone := OnCalcDone; // Zeiger auf Ereignis merken fBitmap := TBitmap.Create; // TBitmap anlegen fBitmap.Assign( Bitmap ); // Bild kopieren Inherited Create( False ); // Thread erstellen Priority := tpIdle; // geringe Priorität FreeOnTerminate := True; // Instanz selbst freigeben <- hmmm, nicht geschickt End;
Delphi-Quellcode:
sollte allerdings wie folgt aufgebaut werden:
Destructor
Delphi-Quellcode:
In
Destructor TCalcThread.Destroy;
Begin fOnCalcDone:=nil; // Zeiger löschen Inherited; // den Rest ausführen fBitmap.Free; // TBitmap selbst freigeben End;
Delphi-Quellcode:
wird unter anderem
TThread.Destroy
Delphi-Quellcode:
aufgerufen, weil der Thread ja noch aktiv sein kann. Wenn man dem Thread während der Abarbeitung die Ressource fBitmap unter dem A.... wegzieht, was kann dann passieren? Genau, es knallt. Also erst
TThread.Terminate
Delphi-Quellcode:
, danach ist der Thread gesichert beendet und dann können alle Ressourcen ohne Reue freigeben werden!
inherited
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:04 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