Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Thread sauber Beenden ? Handle ungültig. (https://www.delphipraxis.net/107555-thread-sauber-beenden-handle-ungueltig.html)

El.Blindo 29. Jan 2008 18:07


Thread sauber Beenden ? Handle ungültig.
 
Nächster Tag, nächste dumme Frage.

Ich bastel zur Zeit an einem Programm das mit diverse Threads arbeitet, in diesem Beispiel-Thread wird unter anderem ein DLL Funktion importiert, ferner hole ich mir ein Handel im Zusammenhang mit der PDH.DLL.

Dieser Thread läuft in einer Endlos-Schleife (While not Terminated.....).

Nun würde ich gerne beim Beenden des Hauptprogrammes, auch diesen Thread sauber beenden, d.h. meine DLL sowie mein Handle wieder freigeben.

Hier die Thread Unit:

Delphi-Quellcode:
unit PDHThread;

interface

uses
  Classes, Windows, SysUtils;

const
  PDH_NO_DATA                     = $800007D5;
  PDH_MEMORY_ALLOCATION_FAILURE   = $C0000BBB;
  PDH_INVALID_HANDLE              = $C0000BBC;
  PDH_INVALID_ARGUMENT            = $C0000BBD;

  PDH_FMT_RAW                     = $00000010;
  PDH_FMT_ANSI                    = $00000020;
  PDH_FMT_UNICODE                 = $00000040;
  PDH_FMT_LONG                    = $00000100;
  PDH_FMT_DOUBLE                  = $00000200;
  PDH_FMT_LARGE                   = $00000400;
  PDH_FMT_NOSCALE                 = $00001000;
  PDH_FMT_1000                     = $00002000;
  PDH_FMT_NODATA                  = $00004000;
  PDH_FMT_NOCAP100                 = $00008000;

type
  PQUERY             = ^HQUERY;
  HQUERY             = THandle;

  PCOUNTER           = ^HCOUNTER;
  HCOUNTER           = THandle;

  PDH_STATUS         = Longint;

  PPDH_FMT_COUNTERVALUE = ^TPDH_FMT_COUNTERVALUE;
  _PDH_FMT_COUNTERVALUE = record
    CStatus             : DWORD;
    longValue           : Longint;
    doubleValue         : double;
    largeValue          : LONGLONG;
    AnsiStringValue     : LPCSTR;
    WideStringValue     : LPCWSTR;
  end;
  TPDH_FMT_COUNTERVALUE = _PDH_FMT_COUNTERVALUE;
  PDH_FMT_COUNTERVALUE = _PDH_FMT_COUNTERVALUE;

type
  TPDHInfo = record
    Counter : HCounter;
    PDHLoad : PDH_FMT_COUNTERVALUE;
  end;

type
  TPDHThread = class(TThread)
  private
    HQ      : HQuery;
    PDHInfo : Array[1..4] of TPDHInfo;
    hPDH    : THandle;
    procedure UpdatePDH;
  protected
    procedure Execute; override;
  public
  end;

Var
  PdhOpenQuery : function(pReserved: Pointer; dwUserData: DWORD; phQuery: PQUERY): PDH_STATUS; stdcall;
  PdhCloseQuery : function(ahQuery: HQUERY): PDH_STATUS; stdcall;
  PdhAddCounter : function(ahQuery: HQUERY; szFullCounterPath: PChar; dwUserData: DWORD; phCounter: PCOUNTER ): PDH_STATUS; stdcall;
  PdhRemoveCounter : function( ahCounter: HCOUNTER ): PDH_STATUS; stdcall;
  PdhCollectQueryData : function( ahQuery: HQUERY ): PDH_STATUS; stdcall;
  PdhValidatePath : function( szFullCounterPath: PChar ): PDH_STATUS; stdcall;
  PdhGetFormattedCounterValue : function( ahCounter: HCOUNTER; dwFormat: DWORD; lpdwType: LPDWORD; pValue: PPDH_FMT_COUNTERVALUE): PDH_STATUS; stdcall;

implementation

uses Main;

function GetNumberOfProcessors: Integer;
var
  SystemInfo: TSystemInfo;
begin
  GetSystemInfo(SystemInfo);
  Result:=SystemInfo.dwNumberOfProcessors;
end;

Function LoadPDH : THandle;
Var
  H : THandle;
Begin
  H := LoadLibrary('PDH.DLL');
  If H <> 0 then
  Begin
    PdhOpenQuery               := GetProcAddress(H, 'PdhOpenQuery');
    PdhCloseQuery              := GetProcAddress(H, 'PdhCloseQuery');
    PdhAddCounter              := GetProcAddress(H, 'PdhAddCounterA');
    PdhRemoveCounter           := GetProcAddress(H, 'PdhRemoveCounter');
    PdhCollectQueryData        := GetProcAddress(H, 'PdhCollectQueryData');
    PdhValidatePath            := GetProcAddress(H, 'PdhValidatePath');
    PdhGetFormattedCounterValue := GetProcAddress(H, 'PdhGetFormattedCounterValue');
  End;
  Result := H;
End;

procedure TPDHThread.Execute;
var
  X, MaxX  : Integer;
  dwctrType : DWord;
begin
  hPDH := LoadPDH;
  If hPDH <> 0 then
  Begin
    If PDHOpenQuery(nil, 1 ,@HQ) = ERROR_SUCCESS then
    Begin
      MaxX := GetNumberOfProcessors;
      If MaxX > 4 then
        MaxX := 4;
      For X := 1 to MaxX do
        If PDHAddCounter(HQ, PChar('\Prozessor('+InttoStr(X-1)+')\Prozessorzeit (%)'), 1, @PDHInfo[X].Counter) <> ERROR_SUCCESS then
          PDHAddCounter(HQ, PChar('\Processor('+InttoStr(X-1)+')\% Processor Time'), 1, @PDHInfo[X].Counter);
      while not Terminated do
      Begin
        If PDHCollectQueryData(HQ) = ERROR_SUCCESS then
          For X := 1 to MaxX do
            PdhGetFormattedCounterValue(PDHInfo[X].Counter, PDH_FMT_DOUBLE, @dwctrType, @PDHInfo[X].PDHLoad);
        Synchronize(UpdatePDH);
        Sleep(1000);
      End;
    PdhCloseQuery(HQ);
    FreeLibrary(hPDH);
    End;
  End;
end;

procedure TPDHThread.UpdatePDH;
begin
  MainForm.UpdatePDH(PDHInfo);
end;

end.
Dieser Thread wird im Hauptprogramm folgendermaßen gestartet:

Delphi-Quellcode:
  // Thread zur Berechnung des CPULoad starten
  PDHThread := TPDHThread.Create(True);
  PDHThread.Priority := tpNormal;
  PDHThread.FreeOnTerminate := True;
  PDHThread.Resume;
Beim Testen des Ganzen bin ich jedoch darauf gestoßen das weder PdhCloseQuery noch FreeLibrary ausgeführt werden.

Also hab ich das Ganze geändert, so:

Delphi-Quellcode:
  // Thread zur Berechnung des CPULoad starten
  PDHThread := TPDHThread.Create(True);
  PDHThread.Priority := tpNormal;
  PDHThread.FreeOnTerminate := False;
  PDHThread.Resume;
Und habe im OnDestroy Event (wahlweise auch im OnCloseQuery) des Hauptprogrammes folgendes eingeführt:

Delphi-Quellcode:
  // Thread zur Berechnung des CPULoad beenden
  PDHThread.Terminate;
  PDHThread.Waitfor;
  PDHThread.Free;
Nun werden PdhCloseQuery und FreeLibrary ausgeführt, allerdings fange ich mir Exceptions.

Handle ist ungültig(6) oder Read of Address 0001.

Wo liegt mein Fehler ?

Wenn ich FreeOnterminate auf True setze ist es für mich noch verständlich, offensichtlich wird der Thread abgewürgt bevor PdhCloseQuery und FreeLibrary ausgeführt werden.

Bei FreeOnterminate auf False, wenn ich den Thread also selber beende, müssten doch die Handles auf hPDH und HQ weiter gültig sein, zumindestens so lange bis ich sie selber frei gebe, oder PDHThread.Free aufrufe. ODER ?

MfG

El.Blindo

messie 29. Jan 2008 18:43

Re: Thread sauber Beenden ? Handle ungültig.
 
Ich hab' nicht genau durchschaut, was Du da tust, mir ist aber etwas aufgefallen.
Im Thread deklarierst Du
Delphi-Quellcode:
private
    PDHInfo : Array[1..4] of TPDHInfo;
Im execute rufst Du
Delphi-Quellcode:
MainForm.UpdatePDH(PDHInfo);
auf. Das finde ich etwas komisch. Bei der Variante FreeOnTerminate machst Du PDHInfo platt, obwohl Du evtl. noch im synchronize() steckst, das ist riskant. Ob das auch ohne FreeOnTerminate so ist, kann ich nicht überblicken, aber generell: eine private-Variable eines nachgeordneten Thread an den Hauptthread zu übergeben ist mir unheimlich.
Warum deklarierst Du PDHInfo nicht public im HauptThread?

Den Aufruf von PDHCloseQuery etc. bekommst Du meiner Meinung nach hin, indem Du die while not-Schleife in eine try..finally-Struktur packst - jedenfalls mache ich das so. Dann kannst Du im finally reproduzierbar aufräumen.

Grüße, Messie

El.Blindo 29. Jan 2008 20:04

Re: Thread sauber Beenden ? Handle ungültig.
 
Danke, hast mir schon mal sehr geholfen.

Das Problem war das ich auch die Handles im Public erstellt habe und daher auch zu früh platt gemacht habe.

Daran gedacht, die PDHInfo im Hauptprogramm Public zu erstellen habe ich auch schon, mir ist allerdings nicht klar wie ich dann von beiden Threads auf diese Variable zugreifen kann, ohne das es Probleme gibt.

Also wie ich das dann Synchronisized hin bekomme.

MfG

El.Blindo

Dani 29. Jan 2008 20:26

Re: Thread sauber Beenden ? Handle ungültig.
 
Hi, so gäbe es etwas weniger Probleme mit der Sichtbarkeit usw... ist aber auch noch nicht so toll, weil sehr speziell.
Delphi-Quellcode:
unit PDHThread;

interface

  {...}

  TPDHThreadEvent = class(TObject)
  private
    FUsage: Double;
    FCPUIndex: Integer;
  public
    constructor Create(Info: TPDHInfo; CPUIndex: Integer);
  published
    property Usage: Double read FUsage write FUsage;
    property CPUIndex: Integer read FCPUIndex write FCPUIndex;
  end;

  {...}

implementation

  {...}

procedure TPDHThread.UpdatePDH;
var I: Integer;
    Event: TPDHThreadEvent;
begin
  for I := Low(PDHInfo) to High(PDHInfo) do begin
    try
      Event := TPDHThreadEvent.Create(PDHInfo[i], i);
      MainForm.UpdatePDH(Event);
    finally
      Event.Free;
    end;
  end;
end;

{ TPDHThreadEvent }

constructor TPDHThreadEvent.Create(Info: TPDHInfo; CPUIndex: Integer);
begin
  inherited Create;
  FUsage := Info.PDHLoad.doubleValue;
  FCPUIndex := CPUIndex;
end;

end.

alzaimar 29. Jan 2008 20:36

Re: Thread sauber Beenden ? Handle ungültig.
 
Der Fehler ('Das Handle ist ungültig') tritt bei Threads bekanntermaßen dann auf, wenn die Classes-Unit finalisiert wurde und DANACH noch Threads freigegeben werden. TThread.Destroy verwenden Objekte aus der Classes-Unit.

Aber das scheint bei Dir nicht der Fall zu sein. step doch einfach mal den Thread durch (NICHT das Synchronize, das macht keinen Spass).

Ich meine, das Dein Code sauber ist, vor allen Dingen mit der manuellen Freigabe.

Erstelle Dir ein kleines Projekt mit zwei Buttons.
Button1 = Thread-Instantiierung und -Start
Button2 = Thread-Ende und Destroy.

Klappt das dann auch?

Andy090778 29. Jan 2008 22:48

Re: Thread sauber Beenden ? Handle ungültig.
 
Ich hatte auch schon mal Probleme beim Ausführen von Code nach dem Beenden durch ein Terminate bzw. ein Free.

Mein Fazit:
  • - Ohne WaitFor darf man kein Thread manuell beenden. Klingt logisch, vor allem dann wenn der Thread Daten mit irgendwas anderem syncronisiert. Somit ist sichergestellt, dass alle Syncronized-Beziehungen beendet wurden.
  • - Aufräumarbeiten hab ich strikt in OnTerminated-Ereignis benutzt. Da ich sowieso außerhalb des Threads die Strukturen und Daten bereitstelle, hatte ich da kein Problem. Wenn Du eine Komponente machen möchtest, kannst Du ja trotzdem den Code in eine andere Unit packen.

Hier nochmal die Beschreibung aud Delphi:
Mit WaitFor ermitteln Sie den Wert von ReturnValue, wenn die Ausführung des Thread beendet ist. WaitFor kehrt erst zurück, wenn der Thread beendet ist. Dazu muß der Thread entweder die Methode Execute abschließen oder die Beendigung einleiten, sobald die Eigenschaft Terminated den Wert True annimmt.

Das fette ist der springende Punkt. Ich vermute mal bis die 1000ms Wartezeit vergehen, ist das Terminated Bit schon gesetzt und die internen Daten sind freigegeben. Dann ist auch Dein Handle nicht mehr gültig.

Abhilfe:

Ich würde ganz einfach mal:
Delphi-Quellcode:
    PdhCloseQuery(HQ);
    FreeLibrary(hPDH);
in eine Prozedur verschieben, die Du in der Prozedur Create an OnTerminate hängst.

Und aus
Delphi-Quellcode:
Array[1..4] of TPDHInfo
würd ich auch ein Typ machen.

So lass ich zb. in einem Rechnungsprogramm verzögert die Daten aus einer Datenbank lesen und in ein Listview schreiben. Und der Benutzer kann jederzeit durch ESC o.ä. den Vorgang abbrechen.

Andreas

Dani 30. Jan 2008 00:13

Re: Thread sauber Beenden ? Handle ungültig.
 
Zitat:

Zitat von Andy090778
...die Methode Execute abschließen oder die Beendigung einleiten, sobald die Eigenschaft Terminated den Wert True annimmt.
Das fette ist der springende Punkt. Ich vermute mal bis die 1000ms Wartezeit vergehen, ist das Terminated Bit schon gesetzt und die internen Daten sind freigegeben. Dann ist auch Dein Handle nicht mehr gültig.

Sleep kehrt erst zurück, wenn die Zeit abgelaufen ist. http://img145.imageshack.us/img145/4...teng101qt5.gif

Andy090778 30. Jan 2008 20:31

Re: Thread sauber Beenden ? Handle ungültig.
 
Hier verdeutlicht der Ablauf:

Delphi-Quellcode:
  PDHThread.Terminate;
// setzt Terminate im Thread, nun läuft die 1000ms Wartezeit ab
// ...
  PDHThread.Waitfor;
// Delphi wartet bis Terminated true wird oder Execute-Procedur beendet wird.
// ...
// Mittlerweile wird Terminated true
// Nun Springt Delphi zum nächsten Befehl
// Die Wartezeit ist noch nicht um
  PDHThread.Free;
// jetzt wird alles freigegeben
// Die Wartezeit ist noch nicht um

// Der Thread ruft jetzt erst die zwei anderen Befehle auf. Und hier knallts.

Dani 30. Jan 2008 21:00

Re: Thread sauber Beenden ? Handle ungültig.
 
Achso, da hab ich dich falsch verstanden :stupid: . Allerdings stimmt das hier zumindest für Delphi 6 nicht, denn Terminated = true führt nicht dazu, dass Waitfor sofort zurückkehrt.
Delphi-Quellcode:
  PDHThread.Waitfor;
// Delphi wartet bis Terminated true wird oder Execute-Procedur beendet wird.

Ralf107 17. Mär 2008 14:41

Re: Thread sauber Beenden ? Handle ungültig.
 
Ich schlage mich auch schon seit einiger Zeit mit der PDH.dll herum, bisher mit mäßigem Erfolg. :(

Beim Aufruf von PdhGetFormattedCounterValue() stürzt die Software ab, als ob stdcall nicht stimmt oder ein Varaiblentyp falsch deklariert ist.

Zitat:

Zitat von El.Blindo
Delphi-Quellcode:
{...}
  _PDH_FMT_COUNTERVALUE = record
    CStatus             : DWORD;
    longValue           : Longint;
    doubleValue         : double;
    largeValue          : LONGLONG;
    AnsiStringValue     : LPCSTR;
    WideStringValue     : LPCWSTR;
  end;
{...}

Interessante Deklaration! :gruebel:

Zitat:

Zitat von msdn
Delphi-Quellcode:
typedef struct _PDH_FMT_COUNTERVALUE { 
  DWORD CStatus;
  union { // !!!
    LONG longValue;  
    double doubleValue;  
    LONGLONG largeValue;  
    LPCSTR AnsiStringValue;  
    LPCWSTR WideStringValue;
  };
} PDH_FMT_COUNTERVALUE, *PPDH_FMT_COUNTERVALUE;

Müsste der union nicht als varianter Record deklariert werden?

Delphi-Quellcode:
  _PDH_FMT_COUNTERVALUE = record
    CStatus             : DWORD;
    case Integer of // !!!
     0 : ( longValue           : Longint );
     1 : ( doubleValue         : double  );
     2 : ( largeValue          : LONGLONG );
     3 : ( AnsiStringValue     : LPCSTR  );
     4 : ( WideStringValue     : LPCWSTR );
  end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:42 Uhr.
Seite 1 von 2  1 2      

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