AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Rechenintensiven Thread aufräumen

Ein Thema von Schwedenbitter · begonnen am 9. Nov 2014 · letzter Beitrag vom 11. Nov 2014
Antwort Antwort
Seite 1 von 2  1 2      
Schwedenbitter

Registriert seit: 22. Mär 2003
Ort: Finsterwalde
622 Beiträge
 
Turbo Delphi für Win32
 
#1

Rechenintensiven Thread aufräumen

  Alt 9. Nov 2014, 13:00
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:
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.
Da der Code unter Execute sehr kurz ist, macht es keinen Sinn auf Terminated zu reagieren.

Zusatzfrage:
Der Compiler sowie die Hilfe sagen mir, TThread.Resume; sei veraltet. Allerdings erzeugt TThread.Start; bei mir immer eine Exception vom Typ EInvalidPointer . 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.

Wie löse ich diese Probleme?

Gruß, Alex
Alex Winzer
  Mit Zitat antworten Zitat
Benutzerbild von jfheins
jfheins

Registriert seit: 10. Jun 2004
Ort: Garching (TUM)
4.579 Beiträge
 
#2

AW: Rechenintensiven Thread aufräumen

  Alt 9. Nov 2014, 13:11
Ich würde es bei den Speicherlecks lassen - Windows räumt ja auf. Aber wenn es dir so wichtig ist:

Komprimiere nur einen kleinen Teil des Bilds und nehme dies als Anhaltspunkt, welches Format besser ist
Schreibe den JPG/PNG Algorithmus selbst oder binde eine Bibliothek ein, die das unterbrechen gestattet.
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.
  Mit Zitat antworten Zitat
Benutzerbild von BUG
BUG

Registriert seit: 4. Dez 2003
Ort: Cottbus
2.094 Beiträge
 
#3

AW: Rechenintensiven Thread aufräumen

  Alt 9. Nov 2014, 13:42
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.

Hier gibt es die Variante, eine Exception in einem Thread zu erzeugen. Nicht wirklich sauber, aber ziemlich clever.

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?

Der Compiler sowie die Hilfe sagen mir, TThread.Resume; sei veraltet. Allerdings erzeugt TThread.Start; bei mir immer eine Exception vom Typ EInvalidPointer
Erstellte den Thread nicht suspended! Der Konstruktor wird immer in dem Kontext des erstellenden Threads ausgeführt. Das Start ist damit völlig unnötig.

Geändert von BUG ( 9. Nov 2014 um 14:18 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.079 Beiträge
 
Delphi 12 Athens
 
#4

AW: Rechenintensiven Thread aufräumen

  Alt 9. Nov 2014, 14:14
Wie wäre es, wenn man auf das Ende der Threads wartet, bevor sich die Anwendung beendet?
Neuste Erkenntnis:
Seit Pos einen dritten Parameter hat,
wird PoSex im Delphi viel seltener praktiziert.
  Mit Zitat antworten Zitat
Schwedenbitter

Registriert seit: 22. Mär 2003
Ort: Finsterwalde
622 Beiträge
 
Turbo Delphi für Win32
 
#5

AW: Rechenintensiven Thread aufräumen

  Alt 9. Nov 2014, 15:29
Wie wäre es, wenn man auf das Ende der Threads wartet, bevor sich die Anwendung beendet?
So mache ich es im Moment. Aber das Komprimieren je einer JPEG und einer PNG-Datei kann bei einem 200 dpi Bild mit durchschnittlicher CPU mal eben jeweils 8 Sekunden dauern. Bei 10 Bildern will man nicht wirklich warten. Mir wäre es auch egal, weil der Benutzer die Speicherlecks nicht sieht. Aber es ist unsaubere Programmierung.
Alex Winzer
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.079 Beiträge
 
Delphi 12 Athens
 
#6

AW: Rechenintensiven Thread aufräumen

  Alt 9. Nov 2014, 15:53
  • die Thread-Instanz darf sich nicht selber freigeben
  • das Programm ruft Thread.Terminate auf
  • die Threads beenden sich (nach durchschnittlich 4 Sekunden aka 50% von 8 Sekunden)
  • das Programm wartet auf das Ende
  • und beendet sich dann
Neuste Erkenntnis:
Seit Pos einen dritten Parameter hat,
wird PoSex im Delphi viel seltener praktiziert.
  Mit Zitat antworten Zitat
Benutzerbild von BUG
BUG

Registriert seit: 4. Dez 2003
Ort: Cottbus
2.094 Beiträge
 
#7

AW: Rechenintensiven Thread aufräumen

  Alt 9. Nov 2014, 16:01
Aber das Komprimieren je einer JPEG und einer PNG-Datei kann bei einem 200 dpi Bild mit durchschnittlicher CPU mal eben jeweils 8 Sekunden dauern.
Nochmal: Du schmeißt 8 Sekunden Arbeitszeit einfach weg, wenn du die Größe hast

Bei 10 Bildern will man nicht wirklich warten.
Eventuell könnte dir ein Threadpool gut tun. Wenn die 10 Threads nebenläufig rödeln, kommen die sich vermutlich bloß in die Quere.
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.
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

Registriert seit: 29. Mai 2002
37.621 Beiträge
 
Delphi 2006 Professional
 
#8

AW: Rechenintensiven Thread aufräumen

  Alt 10. Nov 2014, 01:28
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.
Michael
Ein Teil meines Codes würde euch verunsichern.
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.079 Beiträge
 
Delphi 12 Athens
 
#9

AW: Rechenintensiven Thread aufräumen

  Alt 10. Nov 2014, 09:45
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.
Neuste Erkenntnis:
Seit Pos einen dritten Parameter hat,
wird PoSex im Delphi viel seltener praktiziert.
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#10

AW: Rechenintensiven Thread aufräumen

  Alt 10. Nov 2014, 12:08
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 TThread.FreeOnTerminate := True; wegdelegieren.

BTW: Das hier ist grober Unfug:
Delphi-Quellcode:
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;
Während der Constructor abgearbeitet wird wird der Thread nicht loslaufen! Mit der Erkenntnis kann der Code wie folgt geschreiben werden:
Delphi-Quellcode:
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;
Der Destructor sollte allerdings wie folgt aufgebaut werden:
Delphi-Quellcode:
Destructor TCalcThread.Destroy;
Begin
   fOnCalcDone:=nil; // Zeiger löschen

   Inherited; // den Rest ausführen

   fBitmap.Free; // TBitmap selbst freigeben
End;
In TThread.Destroy wird unter anderem TThread.Terminate 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 inherited , danach ist der Thread gesichert beendet und dann können alle Ressourcen ohne Reue freigeben werden!
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


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 21:12 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