AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Multimedia Delphi EOutOfResources - Wie viele Handles sind noch verfügbar?

EOutOfResources - Wie viele Handles sind noch verfügbar?

Ein Thema von berens · begonnen am 19. Dez 2019 · letzter Beitrag vom 19. Dez 2019
Antwort Antwort
berens

Registriert seit: 3. Sep 2004
409 Beiträge
 
Delphi 2010 Professional
 
#1

EOutOfResources - Wie viele Handles sind noch verfügbar?

  Alt 19. Dez 2019, 14:41
Hallo zusammen.

Ich habe das Problem, dass mein Programm nach mehreren Stunden kommentarlos zugeht. Nach Beseitigung div. technischer Probleme, die für den Thread irrelevant sind, konnte ich das Programm nun endlich in Delphi / mit Debugger lange genug laufen lassen, um den Fehler angezeigt zu bekommen. Leider war das Programm incl. Delphi soweit aufgehängt, dass das Kopieren der Nachricht nicht möglich war. Das wichtigste konnte ich aber gerade noch ablesen: Es war -in diesem konkreten Fall- eine EOutOfResources Exception, die auslösende Prozedur konnte er mir auch gerade noch anzeigen. Falls dies in allen Fällen, in den das Programm kommentarlos zugegangen ist, die Ursache war, besteht ja Chance auf Besserung .

Der Fehler konnte auch schnell gefunden werden; durch eine falsche Verschachtelung wurde ein mit GetDC erhaltenes Handle nicht wieder freigegeben, und diese Prozedur wird aktuell ~4x Sekunde aufgerufen, also kommen pro Stunde knapp 10'000 neue Handles dazu, die nicht freigegeben werden - klar, dass das System irgendwann streikt. Ich glaube, auch ein zweites TBitmap wurde nicht wieder freigegeben.

Was mich nun akut irritiert:
-Im Taskmanager waren CPU (hierfür eh irrelevant) und RAM auf Bestwerten. Das Programm verbraucht je nach Arbeitsschritt mal mehr, mal weniger RAM, pendelt sich aber immer wieder bei ~15 MB ein. Also ist die Resource "RAM" schon mal nicht aufgebraucht.
-Ich hatte bis dato immer regelmäßig
Delphi-Quellcode:
  CoFreeUnusedLibraries;
  SetProcessWorkingSetSize(GetCurrentProcess, $FFFFFFFF, $FFFFFFFF);// DefragMem;
aufgerufen. Evtl. sorgte das dafür, dass zwar der RAM von dem vergessenen TBitmap wieder freigegeben wurde und ich es deshalb im Taskmanager nicht als steigender Ram-Verbrauch gesehen habe...
-Wahrscheinlich bekomme ich gleich von euch einen freundlichen Hinweis, das "Handle"s nichts (direkt) mit RAM zu tun haben, und man die genutzten/"verbrauchten" Handles entsprechend nirgendwo sehen kann. Wäre irgendwo logisch. Die "Handles" im Taskmanager bei "Leistung" - "CPU" lassen übrigens keinen großen Rückschluss darauf zu, ob/dass mein Programm viele Handles verbraucht. Die Werte schwanken so stark durch Windows-Hintergrundprozesse, dass eine klare Aussage nicht möglich ist, was ich damit zu tun habe. Eine klar Zunahme von 10'000 Handles die Stunde ist auf jeden Fall nicht feststellbar... Bei "Resourcenmonitor" - "CPU" - [meine .exe] - "Zugeordnete Handles" ist die Liste auch mehrere Minuten nach dem Start noch unverändert...
-EDIT: Ich hatte EurekaLog 7.0.7 in meinem Programm mitlaufen - Das Springt leider bei dem Fehler nicht an. Wahrscheinlich, weil es zum Erzeugen der Fehlermeldung ein freies Handle braucht.

Meine Fragen zu dem Thema:
1) (Wie) kann ich
-mit meinem Programm, oder:
-mit Windows
auslesen, wie viele Handles (von meinem Programm!) benutzt wurden, oder alternativ: noch zur Verfügung stehen. Ich könnte mir vorstellen, dass ich in dieser einen Prozedur zwar irgendwann bei "GetDC()" "0" erhalte (--> fehlgeschlagen) und dann wissen würde, dass es ab /jetzt/ einen Fehler gibt, weil keine freien Handles mehr verfügbar sind (bzw. dann würde wohl eher die o.g. Exception ausgelöst), aber ich würde gerne "Überwachungsmäßig" über die ganze Programmlaufzeit beobachten, ob/wie sich mein Handleverbrauch entwickelt, und ob der aktuelle Trend vermuten lässt, dass das Programm bald irgendwann mangels freier Handles abstürzen wird. Oder auch für meine Logging-Funktion: freie Handles vor/nach Durchlauf der Prozedur

2) Eine Bitte: Schaut euch die nachfolgende Prozedur an, und sagt mir, ob in der jetzigen, korrigierten Form Bedenken für Speicherverbrauch oder nicht freigegebene Handles bestehen:

Delphi-Quellcode:
// Prüft, ob der Inhalt von _Parent komplett Schwarz ist
// Die Prozedur zeichnet, FALLS übergeben, auf _TargetBitmap, was sie "gesehen" hat
// Zum Vermeidung div. Probleme wird der untersuchte Bereich auf 640x480 px skaliert,
// das ist für diese Zwecke mehr als ausreichend genau
function EndOfPresentation_Neu(_Parent: TWinControl; _TargetBitmap: Graphics.TBitmap): Boolean;
var
  myBitmap: graphics.TBitmap;
  DetectHeight, DetectWidth: Integer;
  SourceDC: HDC;

  hwParentHandle: HWND;
const
  CAPTUREBLT = $40000000;
  SCAN_WIDTH = 640;
  SCAN_HEIGHT = 480;
begin
  Result := False;

  // Variablen auf gültige Werte initialisieren, damit sie
  // auf jeden Fall korrekt freigegeben werden können
  hwParentHandle := 0;
  myBitmap := NIL;

  try
    // Kein Parent, keine Analyse
    if not assigned(_Parent) then Exit;
    if not _Parent.InheritsFrom(TWinControl) then Exit;

    DetectWidth := _Parent.Width;
    DetectHeight := _Parent.Height;

    if (DetectHeight < 1) or (DetectWidth < 1) then Exit;

    // TBitmap initialisieren, und auf die erwartete Größe bringen
    try
      hwParentHandle := _Parent.Handle;

      myBitmap := Graphics.TBitmap.Create;
      myBitmap.PixelFormat := pf32bit;

      // Zu analysierender Bereich ist lediglich 640 x 480 px
      myBitmap.Width := SCAN_WIDTH;
      myBitmap.Height := SCAN_HEIGHT;
    except
      on E: Exception do begin
        log('EndOfPresentationDetector.EndOfPresentation_Neu.InitializeValues.Exception: ' + E.Message);
      end;
    end;

    // Screenshot vom betroffenen Bereich erstellen
    if assigned(myBitmap) then begin
      try
        SourceDC := GetDC(hwParentHandle);

        if SourceDC <> 0 then begin
          // das "_Parent"-Objekt abfotografieren. Wir skalieren gleichzeitig auf 640 x 480. Hinreichend genau für unseren Zweck.
          StretchBlt(myBitmap.Canvas.Handle, 0, 0, SCAN_WIDTH, SCAN_HEIGHT, SourceDC, 0, 0, DetectWidth, DetectHeight, SRCCOPY or CAPTUREBLT);

          ReleaseDC(hwParentHandle, SourceDC);
        end;
      except
        on E: Exception do begin
          log('EndOfPresentationDetector.EndOfPresentation_Neu.DoBitBlt.Exception: ' + E.Message);
        end;
      end;

      try
        // Prozedur, die nur auf die Scanlines von myBitmap zugreift, und zurückgibt, Keine Leak/Handle Bedenken!
        // Gibt an wie viele Prozent des Bildes explizit schwarz sind,
        // bzw. bei wieviel Prozent der Höhes des Bildes (von Oben gesehen)
        // die letzte Zeile mit mind. einem nicht-schwarzen Pixel ist
        Result := (ObereKanteNurSchwarzInProzent(myBitmap) < 25);

        // Falls der Parameter übergeben wurde, wird auf _TargetBitmap das erfasste, nun skalierte und mit
        // Hilfslininen versehene Bild ausgegeben, so dass es dem Benutzer angezeigt werden könnte
        if assigned(_TargetBitmap) then begin
          _TargetBitmap.Assign(myBitmap);
        end;
      except end;

      FreeAndNil(myBitmap);
    end;
  except
    on E: Exception do begin
      log('EndOfPresentationDetector.EndOfPresentation_Neu.Global.Exception: ' + E.Message);
    end;
  end;
end;
Vielen Dank
Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit

Geändert von berens (19. Dez 2019 um 14:48 Uhr)
  Mit Zitat antworten Zitat
Rolf Frei

Registriert seit: 19. Jun 2006
264 Beiträge
 
Delphi 10.3 Rio
 
#2

AW: EOutOfResources - Wie viele Handles sind noch verfügbar?

  Alt 19. Dez 2019, 15:10
Du solltest da unbedingt mit try/finally arbeiten. Wenn du da irgendwo ausserhalb deiner unnötigen Exceptions eine Exception bekommst, hast du danach zumindest für das Bitmap ein Memoryleak. Dass du da so viele try/excepts drin hast macht keinen Sinn. So wie du das da machst reicht einer um den ganze Code. Oder willst du wirklich, dass Teile deines Codes noch ausgeführt werden, wenn davor eine Exception aufgetreten ist?

Bitte schütze deine erstellten Resourcen mit einem try/finally und alles ist bestens. Sobald du ein Objekt erstellst, kommt immer ein try und am Ende ein finally in dem du das Objket wieder frei gibst. Wenn du dich daran hälst wirst du nie mehr Probleme dieser Art haben.

Für ein Tool, das dir die allozierten Handels und noch viel mehr zeigt, emfehle ich dir den Prozess Explorer von SysInternals (Microsoft).

https://docs.microsoft.com/en-us/sys...ocess-explorer

Delphi-Quellcode:
// Prüft, ob der Inhalt von _Parent komplett Schwarz ist
// Die Prozedur zeichnet, FALLS übergeben, auf _TargetBitmap, was sie "gesehen" hat
// Zum Vermeidung div. Probleme wird der untersuchte Bereich auf 640x480 px skaliert,
// das ist für diese Zwecke mehr als ausreichend genau
function EndOfPresentation_Neu(_Parent: TWinControl; _TargetBitmap: Graphics.TBitmap): Boolean;
var
  myBitmap: graphics.TBitmap;
  DetectHeight, DetectWidth: Integer;
  SourceDC: HDC;
  State: String;
  hwParentHandle: HWND;
const
  CAPTUREBLT = $40000000;
  SCAN_WIDTH = 640;
  SCAN_HEIGHT = 480;
begin
  Result := False;

  // Variablen auf gültige Werte initialisieren, damit sie
  // auf jeden Fall korrekt freigegeben werden können
  hwParentHandle := 0;
  myBitmap := NIL;

  try
    // Kein Parent, keine Analyse
    if not assigned(_Parent) then Exit;
    if not _Parent.InheritsFrom(TWinControl) then Exit;

    DetectWidth := _Parent.Width;
    DetectHeight := _Parent.Height;

    if (DetectHeight < 1) or (DetectWidth < 1) then Exit;

    // TBitmap initialisieren, und auf die erwartete Größe bringen
    State := 'EndOfPresentationDetector.EndOfPresentation_Neu.InitializeValues.Exception: ';
    hwParentHandle := _Parent.Handle;

    myBitmap := Graphics.TBitmap.Create;
    try
      myBitmap.PixelFormat := pf32bit;

      // Zu analysierender Bereich ist lediglich 640 x 480 px
      myBitmap.Width := SCAN_WIDTH;
      myBitmap.Height := SCAN_HEIGHT;

      // Screenshot vom betroffenen Bereich erstellen
      State := 'EndOfPresentationDetector.EndOfPresentation_Neu.DoBitBlt.Exception: ';
      SourceDC := GetDC(hwParentHandle);
      if SourceDC <> 0 then begin
        try // Dieses try/finally ist eigentlch überflüssig, da es hier keine Exception geben sollte
          // das "_Parent"-Objekt abfotografieren. Wir skalieren gleichzeitig auf 640 x 480. Hinreichend genau für unseren Zweck.
          StretchBlt(myBitmap.Canvas.Handle, 0, 0, SCAN_WIDTH, SCAN_HEIGHT, SourceDC, 0, 0, DetectWidth, DetectHeight, SRCCOPY or CAPTUREBLT);
        finally
          ReleaseDC(hwParentHandle, SourceDC);
        end;
      end;

      State := 'EndOfPresentationDetector.EndOfPresentation_Neu.ObereKanteNurSchwarzInProzent.Exception: ';
      // Prozedur, die nur auf die Scanlines von myBitmap zugreift, und zurückgibt, Keine Leak/Handle Bedenken!
      // Gibt an wie viele Prozent des Bildes explizit schwarz sind,
      // bzw. bei wieviel Prozent der Höhes des Bildes (von Oben gesehen)
      // die letzte Zeile mit mind. einem nicht-schwarzen Pixel ist
      Result := (ObereKanteNurSchwarzInProzent(myBitmap) < 25);

      // Falls der Parameter übergeben wurde, wird auf _TargetBitmap das erfasste, nun skalierte und mit
      // Hilfslininen versehene Bild ausgegeben, so dass es dem Benutzer angezeigt werden könnte
      if assigned(_TargetBitmap) then begin
        _TargetBitmap.Assign(myBitmap);
      end;

    finally
      FreeAndNil(myBitmap); // wird so immer freigeben, auch im Fall einer Exception
    end;

  except
    on E: Exception do begin
      log(State + E.Message);
    end;
  end;
end;

Geändert von Rolf Frei (19. Dez 2019 um 15:26 Uhr)
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
2.460 Beiträge
 
Delphi 10.2 Tokyo Enterprise
 
#3

AW: EOutOfResources - Wie viele Handles sind noch verfügbar?

  Alt 19. Dez 2019, 15:11
Könnte hilfreich sein:
https://www.nirsoft.net/utils/gdi_handles.html
https://www.nirsoft.net/utils/handle_counters_view.html
  Mit Zitat antworten Zitat
berens

Registriert seit: 3. Sep 2004
409 Beiträge
 
Delphi 2010 Professional
 
#4

AW: EOutOfResources - Wie viele Handles sind noch verfügbar?

  Alt 19. Dez 2019, 17:05
Hallo,
vielen Dank euch Beiden für die Rückmeldungen.

Die Tools TiGü belegen direkt, dass meine alte Version tatsächlich mit den GDI-Handels nach oben geht. Zwar "nur" ca. 10 pro Minuten, aber mit entsprechend langer Laufzeit führt das unweigerlich zu der o.g. Exception.

Der ProcessExplorer von Rolf ist ja auch von Sysinternals und bekommt das bestimmt auch hin. Danke auch Dir für diesen Tip.

Bzgl. der Exceptions:
Manchmal sieht man echt den Wald vor lauter Bäumen nicht. Ich war bis dato -weshalb auch immer- der Meinung, dass alle Exceptions, die nicht unmittelbar mit try..except abgefangen werden, an das GUI eskaliert werden: deshalb fange ich an jeder möglichen Stelle eben genau dies so ab. try..finally war für mich bis dato die unnützeste Funktion in ganz Delphi, weil: alles, was nach einer Exception gemacht werden muss, schreibe ich einfach nach try..except, denn "diese Zeile wird ja auch nach der Exception ausgeführt".

In meinem Beispielcode müsste -in genau dieser Konstellation- myBitmap stets freigegeben werden, da alle vorherigen Exceptions (ja: unnötig oft) abgefangen und zumindest mit Logbuch behandelt werden. Zwar kann FreeAndNil(myBitmap) hier nur aufgerufen werden, wenn dieses vorher "Assigned()" war, aber wenn es nicht assigned war, muss es ja auch nicht freigegeben werden, oder? Ich will hier nicht in den Krümeln suchen, nur der Vollständigkeit halber helft mir bitte es zu verstehen, falls ich hier falsch liege. Rolfs Variante ist unabhängig davon die Bessere, die ich auch zukünftig verwenden werde.

Die Kritik an meinem ReleaseDC ist zu 100% berechtigt.

Deinen Ansatz, Rolf, mit try..finally *umgeben* von try..except habe ich mir eben mir einer kleinen Demo reingezogen:
Delphi-Quellcode:
procedure TfrmDemo1.Button1Click(Sender: TObject);
var
  tmp: TButton;
begin
  try
    try
      tmp := NIL;
      tmp.Click;
    finally
      ShowMessage('finally');
    end;

    ShowMessage('2');
  except
    ShowMessage('except');
  end;
end;
Dadurch, dass die Exception *nicht* ans GUI eskaliert wird (das dachte ich früher wohl nur, weil ich damals try..finally nicht von try..except nochmal umgeben hatte, und es in diesem Fall tatsächlich ans GUI ging), sondern erst die Finally behandlung stattfindet und *dann* die Exception-Behandlung ist das natürlich des wesentlich bessere Weg. Es ist immer wieder erschreckend, dass auch nach so vielen Jahren Delphi-Erfahrung solche Grundlagen erst tatsächlich "verstanden" werden. Schämschäm. Aber besser spät als nie.

Danke nochmal!
Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
  Mit Zitat antworten Zitat
Rolf Frei

Registriert seit: 19. Jun 2006
264 Beiträge
 
Delphi 10.3 Rio
 
#5

AW: EOutOfResources - Wie viele Handles sind noch verfügbar?

  Alt 19. Dez 2019, 18:03
Das try/finally ist eigentlich eines der wichtigsten Konstrukte und du solltest dir angewöhnene damit zu arbeiten. Deine Variante mit den vielen try/except würde zwar auch funktionieren, aber ist meiner Meinung nach viel zu kompliziert. Ausserdem ist es nicht unbedingt die fein Art da einfach alle Exception zu "schlucken". Wie weist du denn ausserhalb deiner Routine, ob alles sauber verabreitet wurde? Wertest du da nach jedem Aufruf das Log aus? Das kann ich mir nicht vorstellen. Ich mache für solche Situationen in der Regel auch eigene Exceptions und frage die dann extern ab, damit ich weiss was da genau passiert ist und wie ich auf die jeweilige Situation reagieren muss.

OK in deinem Beispiel mit dem Button1Click Event macht das keinen Sinn, da du das nicht selber aufrufst, aber in einer Funtkion die du an verschiedenen Stellen aufrufst, ist das eine dumme Idee einfach alle Exceptions zu verschlucken.

Hier nochmal ein kleines Beispiel, wenn das Bestandteil einer Funktion wäre. Ob du das except oder das finally zu oberst hast, hängt vom jeweiligen Code ab. Du bist da eigentlich frei wie du das machst. Der Auswertung erfolgt immer vom innersten Block nach aussen. Also hier wird zuerst das Except behandelt und erst danach das Finally. In deinem Beisiel ist es umgekehrt, weil da das Finally zu innerst ist.
Delphi-Quellcode:
type
  EMyNilException = class(Exception);

procedure DoSomething();
var
  tmp: TButton;
begin
  try
    try
      tmp := NIL;
      if tmp = nil then
        raise EMyNilException.Create('tmp ist NIL!');
      tmp.Click;
    except
      on E: EMyNilException do
      begin
        ShowMessage(e.Message);
        raise;
      end
      else
        raise; // Exception die wir nicht selber behandeln sollten erneut ausgelöst werden.
    end;

    ShowMessage('2');
  finally
    ShowMessage('finally');
  end;
end;

begin
  try
    DoSoemthing;
  except
    on E: EMyNilException do
      ShowMessage('Es ist eine NIL-Verletzung aufgetreten');
    else
      raise;
  end;
end

Geändert von Rolf Frei (19. Dez 2019 um 20:52 Uhr)
  Mit Zitat antworten Zitat
Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

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 01:45 Uhr.
Powered by vBulletin® Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2020 by Daniel R. Wolf