Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Freigabe Thread (https://www.delphipraxis.net/206959-freigabe-thread.html)

AJ_Oldendorf 12. Feb 2021 10:42

Freigabe Thread
 
Hallo zusammen,
ich habe folgenden Code (total abgespeckt auf ein Minimum).
Ich create einen Thread mit FreeOnTerminate=False, der tut irgendwas, wird fertig und wird dann wieder gefreet. Trotzdem habe ich beim Beenden ein Speicherleck von dem Objekt obwohl das Destroy vom Thread vorher durchgelaufen wird beim Destroy der Form, dass Objekt auch Nil ist. Könnt ihr mir bitte weiterhelfen?

Delphi-Quellcode:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Generics.Collections, Vcl.StdCtrls;

type
  TSammelListe = Class(TList<TGraphicControl>);

  TAnalyseThread = class(TThread)            
  private
    FList : TSammelListe;

  protected
    procedure Execute; override;
  public
    Status : AnsiString;
    OnReady : TNotifyEvent;

    constructor Create(CreateSuspended : Boolean);
    destructor Destroy; override;
  end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private-Deklarationen }
    AnalyseThread : TAnalyseThread;

    procedure OnAnalyseReady(Sender: TObject);
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.Terminate;
    FreeAndNil(AnalyseThread);
  end;

  AnalyseThread := TAnalyseThread.Create(True);

  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.OnReady := OnAnalyseReady;

    AnalyseThread.Start;
  end;
end;

procedure TForm1.OnAnalyseReady(Sender: TObject);
begin
  //Auswertung des Daten...

  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.Terminate;
    FreeAndNil(AnalyseThread);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.Terminate;
    FreeAndNil(AnalyseThread);
  end;
end;

{ TAnalyseThread }

constructor TAnalyseThread.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);

  FList := TSammelListe.Create;

  FreeOnTerminate := False;
end;

destructor TAnalyseThread.Destroy;
begin
  if Assigned(FList) then
  begin
    FList.Clear;
    FreeAndNil(FList);
  end;

  inherited Destroy;
end;

procedure TAnalyseThread.Execute;
begin
  inherited;

  //Tue irgendwas bis fertig...

  if Assigned(OnReady) then
    OnReady(Self);
end;

end.

himitsu 12. Feb 2021 10:58

AW: Freigabe Thread
 
Also per se stimmt mit TSammelListe eigentlich alles. (im Create erstellen und im Destroy freigeben ... beides auf der selben Ebene ist immer gut)

Bist du sicher, dass das Speicherleck "dieses" Objekt ist und nicht irgendwas Anderes?
Was aber niemand verbieter, ins Create und Destroy der TSammelListe einen Haltepunkt oder Logmeldung einzufügen und zu schauen ob und wie oft das erstellt/freigegeben wird.



Was du eventuell noch bedenken mußt, TThread.Create wird immer im erstellenden Thread ausgeührt,
während bei FreeOnTerminate das Thread.Destroy immer im Thread abläuft.
Und bei einem externen .Free das Thread.Destroy ebenfalls in einem anderen Thread.

Aber manche Dinge müssen/dürfen (nur) im selben Thread genutzt werden, wo sie erstellt wurden.
-> Hier geht also nur TComponent.Create und Component.Free mit einem Try-Finally im Execute.



Und was ist TSammelListe?
"Normalerweise" machen Listen im Free automatisch ein Clear, somit würde ein Free ausreichen.
Delphi-Quellcode:
destructor TAnalyseThread.Destroy;
begin
  FList.Free; // oder FreeAndNil(FList);
  inherited;
end;
[EDIT] Jupp, die Liste gibt "ihre" Items frei, aber nicht die darin gespeicherten Objektreferenzen ... siehe Der schöne Günther

Der schöne Günther 12. Feb 2021 11:02

AW: Freigabe Thread
 
Dein Thread ist in Ordnung, das Problem ist dein
Delphi-Quellcode:
TList<TGraphicControl>
. Die Liste kannst du so oft clearen und freigeben wie du willst, die gibt ihre enthaltenen Elemente nicht selbst frei.

Was du stattdessen willst ist eine
Delphi-Quellcode:
TObjectList<TGraphicControl>
.

AJ_Oldendorf 12. Feb 2021 11:02

AW: Freigabe Thread
 
Liste der Anhänge anzeigen (Anzahl: 1)
Denk dir die Sammelliste weg, damit hat es nichts zu tun (habe alles zur Sammelliste gelöscht, gleiches Speicherleck -> siehe Anhang).
Die Liste ist es nicht, es ist wirklich der Thread...

AJ_Oldendorf 12. Feb 2021 11:03

AW: Freigabe Thread
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1482824)
Dein Thread ist in Ordnung, das Problem ist dein
Delphi-Quellcode:
TList<TGraphicControl>
. Die Liste kannst du so oft clearen und freigeben wie du willst, die gibt ihre enthaltenen Elemente nicht selbst frei.

Was du stattdessen willst ist eine
Delphi-Quellcode:
TObjectList<TGraphicControl>
.


Nein, siehe mein Beitrag gerade eben. Die Liste kann man ausklammern, selber Fehler

Delphi-Quellcode:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TAnalyseThread = class(TThread)
  private
  protected
    procedure Execute; override;
  public
    Status : AnsiString;
    OnReady : TNotifyEvent;

    constructor Create(CreateSuspended : Boolean);
    destructor Destroy; override;
  end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private-Deklarationen }
    AnalyseThread : TAnalyseThread;

    procedure OnAnalyseReady(Sender: TObject);
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.Terminate;
    FreeAndNil(AnalyseThread);
  end;

  AnalyseThread := TAnalyseThread.Create(True);

  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.OnReady := OnAnalyseReady;

    AnalyseThread.Start;
  end;
end;

procedure TForm1.OnAnalyseReady(Sender: TObject);
begin
  //Auswertung des Daten...

  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.Terminate;
    FreeAndNil(AnalyseThread);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.Terminate;
    FreeAndNil(AnalyseThread);
  end;
end;

{ TAnalyseThread }

constructor TAnalyseThread.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);

  FreeOnTerminate := False;
end;

destructor TAnalyseThread.Destroy;
begin
  inherited Destroy;
end;

procedure TAnalyseThread.Execute;
begin
  inherited;

  //Tue irgendwas bis fertig...

  if Assigned(OnReady) then
    OnReady(Self);
end;

end.

himitsu 12. Feb 2021 11:07

AW: Freigabe Thread
 
Da steht doch in dem Dialog aus #4, dass der Thread selbst nicht freigegeben wurde. (was in dem Thread ist, ist demnach eh egal)

PS: Strg+C im Dialog und dann Strg+V hier in ein [QUOTE] ... das wird als "Text" kopiert.

AJ_Oldendorf 12. Feb 2021 11:31

AW: Freigabe Thread
 
Woran erkennst du das? Bei mir steht, ein unerwarteter Speicherverlust ist aufgetreten.
Ist doch ein Speicherleck oder nicht?

---------------------------
Unexpected Memory Leak
---------------------------
An unexpected memory leak has occurred. The unexpected small block leaks are:
53 - 60 bytes: TAnalyseThread x 1

DieDolly 12. Feb 2021 11:57

AW: Freigabe Thread
 
Delphi-Quellcode:
procedure TForm1.OnAnalyseReady(Sender: TObject);
begin
 // Auswertung des Daten...

// if Assigned(AnalyseThread) then
//  begin
//   AnalyseThread.Terminate;
//   FreeAndNil(AnalyseThread);
//  end;
end;
Ändere OnAnalyseReady so ab und dein Problem ist erledigt. Jedenfalls auf Basis des Codes aus dem Beitrag hier drüber.

Hier draus muss man jetzt nicht wieder 5 bis 10 Seiten machen. Einfach in den Editor kopieren, selber testen, Problem gefunden und erledigt.

himitsu 12. Feb 2021 11:58

AW: Freigabe Thread
 
Zitat:

---------------------------
Unexpected Memory Leak
---------------------------
An unexpected memory leak has occurred. The unexpected small block leaks are:
53 - 60 bytes: TAnalyseThread x 1
wie groß: was vermutet (wenn erkannt) x wie oft :zwinker:


Zitat:

Ändere OnAnalyseReady so ab und
Ohhh.

OK, sich selbst kann man nicht in sich freigeben.
Ja, hier knallt es dann, aber "leider" werden Exceptions innerhalb von Threads nicht angezeigt.
Delphi fängt solche Exceptions aber ab, genauso wie in FVL/FMX ist überall ein Try-Except drumrum (nur in VCL/FMX dann noch mit einem ShowException).

Aber im Debugger müsstest du diese Exception sehen können :?:

AJ_Oldendorf 12. Feb 2021 12:19

AW: Freigabe Thread
 
Ok, könnt ihr mir noch kurz erklären, warum der Thread im OnAnalyseReady nicht freigegeben werden darf, wenn das doch das Event für mich ist, wo er fertig ist

AJ_Oldendorf 12. Feb 2021 12:19

AW: Freigabe Thread
 
achso, wegen der Freigabe in sich selber... Natürlich. Danke.
Nein, eine Exception kommt nicht.
Danke!

DieDolly 12. Feb 2021 12:22

AW: Freigabe Thread
 
Ich habe davon ehrlich gesagt keine Ahnung. Aber ich schätze, dass das Event im selben Kontext steht wie der Thread ansich. Irgendwie so ...
Einfach den Thread ganz abgekoppelt von außerhalb freigeben.

himitsu 12. Feb 2021 12:22

AW: Freigabe Thread
 
Thread-Exceptions ausgeben
Delphi-Quellcode:
type
  TAnalyseThread = class(TThread)
  protected
    procedure DoTerminate; override; // oder z.B. als MyOnTerminate über OnTerminate
  end;

procedure TAnalyseThread.DoTerminate;
begin
  if Assigned(FatalException) then begin // FatalException ist ausschließlich im OnTerminate/DoTerminate verfügbar
    var S := Exception(FatalException).Message; // Exception oder Message kopieren/klonen, denn später im Queue ist sie schon weg.
    TThread.Queue(nil, procedure
      begin
        MessageBox(Application.MainFormHandle, PChar(S), 'Thread-Error', MB_OK or MB_ICONERROR);
      end);
  end;
  inherited;
end;
oder über das Event
Delphi-Quellcode:
constructor TAnalyseThread.Create(CreateSuspended: Boolean);
begin
  OnTerminate := MyOnTerminate;
  inherited;
end;
oder
Delphi-Quellcode:
procedure TAnalyseThread.Execute;
begin
  try

    ... // hier dazwischen alles erstellen/freigeben ... nichts im Create/Destroy

  except
    on E: Exception do begin
      var S := E.Message; // Exception oder Message kopieren/klonen, denn später im Queue ist sie schon weg.
      Queue(procedure
        begin
          MessageBox(Application.MainFormHandle, PChar(S), 'Thread-Error', MB_OK or MB_ICONERROR);
        end);
    end;
  end;
end;
Und es ist absichtlich Queue anstatt Synchronize, damit ein Deadlock beim Freigeben verhindert wird.
z.B. Thread wartet auf MainThread, aber MainThread wartet im Thread.Free auf das Thread-Ende.


PS:
Delphi-Quellcode:
type
  TAnalyseThread = class(TThread)
  public
    OnReady: TNotifyEvent;
    constructor Create(OnReady: TNotifyEvent=nil);
  end;

constructor TAnalyseThread.Create(OnReady: TNotifyEvent);
begin
  inherited Create(not Assigned(OnReady));
  Self.OnReady := OnReady;
  FreeOnTerminate := False;
end;
und dann anstatt
Zitat:

Delphi-Quellcode:
  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.Terminate;
    FreeAndNil(AnalyseThread);
  end;

  AnalyseThread := TAnalyseThread.Create(True);

  if Assigned(AnalyseThread) then
  begin
    AnalyseThread.OnReady := OnAnalyseReady;

    AnalyseThread.Start;
  end;

nur noch ein
Delphi-Quellcode:
  FreeAndNil(AnalyseThread);
  AnalyseThread := TAnalyseThread.Create(OnAnalyseReady);

TiGü 12. Feb 2021 12:24

AW: Freigabe Thread
 
So eher nicht:

Code:
Unit1.TAnalyseThread.Destroy
System.TObject.Free
Unit1.TForm1.OnAnalyseReady($2E7DDC0)
Unit1.TAnalyseThread.Execute
System.Classes.ThreadProc($2E7DDC0)
System.ThreadWrapper($2E4A4A0)
:7749fa29 KERNEL32.BaseThreadInitThunk + 0x19
:775e76b4 ntdll.RtlGetAppContainerNamedObjectPath + 0xe4
:775e7684 ntdll.RtlGetAppContainerNamedObjectPath + 0xb4
Erzeugen im Mainthread und im TAnalyseThread-Kontext freigeben ist halt eher ungünstig!

Biete folgende Lösung an:
Delphi-Quellcode:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TAnalyseThread = class(TThread)
  private
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
  end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    AnalyseThread: TAnalyseThread;

    procedure OnAnalyseReady(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure TForm1.Button1Click(Sender: TObject);
begin
  if Assigned(AnalyseThread) then
  begin
    FreeAndNil(AnalyseThread);
  end;

  AnalyseThread := TAnalyseThread.Create(True);

  if Assigned(AnalyseThread) then
  begin
    // neu, jetzt noch besser!
    AnalyseThread.OnTerminate := OnAnalyseReady;

    AnalyseThread.Start;
  end;
end;

procedure TForm1.OnAnalyseReady(Sender: TObject);
begin
  // Auswertung des Daten...

  if Assigned(AnalyseThread) then
  begin
    // Wir sind mit allen durch, aber können hier noch nicht freigeben, weil wir noch zu sehr im Sumpf
    // des Synchronize drin sind (siehe procedure TThread.DoTerminate;). Darum noch ne Extra-Runde durch
    // die Windows-Botschaftsbehandlung durch TThread.ForceQueue
    TThread.ForceQueue(nil,
      procedure
      begin
        FreeAndNil(AnalyseThread);
      end);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(AnalyseThread) then
  begin
    FreeAndNil(AnalyseThread);
  end;
end;

{ TAnalyseThread }

constructor TAnalyseThread.Create(CreateSuspended: Boolean);
begin
  FreeOnTerminate := False;
  inherited Create(CreateSuspended);
  TThread.NameThreadForDebugging('TAnalyseThread', Self.ThreadID);
end;

destructor TAnalyseThread.Destroy;
begin
  inherited Destroy;
end;

procedure TAnalyseThread.Execute;
begin
  // Tue irgendwas bis fertig...
end;

end.

DieDolly 12. Feb 2021 12:28

AW: Freigabe Thread
 
Zitat:

TThread.ForceQueue(nil,
Alles ein Glücksspiel. Ich würde prinzipiell nicht im Kontext des Threads freigeben, egal wie, macht man nicht.
Ich schneide mir ja auch nicht den Ast ab, auf dem ich sitze.

Von außen erzeugen und von außen freigeben.

AJ_Oldendorf 12. Feb 2021 12:30

AW: Freigabe Thread
 
Ok, habs verstanden. Ich mache es im OnAnalyseReady einfach nicht un gut ist. Danke

himitsu 12. Feb 2021 12:34

AW: Freigabe Thread
 
Zitat:

Nein, eine Exception kommt nicht.
Nur im Programm oder auch im Debugger?

Wie gesagt, im Programm selber werden unbehandelte Exceptions im Thread von Delphi abgefangen (und dann böswillig nicht ausgegeben).
Zitat:

Zitat von Kurzfassung
Delphi-Quellcode:
function ThreadProc(const Thread: TThread): Integer;
begin
  try
    try
      Thread.Execute;
    except
      Thread.FatalException := AcquireExceptionObject;
    end;
  finally
    Thread.DoTerminate; // die einzige Stelle, wo man an FatalException ran kommt
    if Thread.FreeOnTerminate then
      Thread.Destroy;
  end;
end;

[EDIT]
Wann haben die das umgebaut? bin mir fast sicher FatalException wurde früher vor Destroy freigegeben.
Somit ist es jetzt aber auch im Destroy noch verfügbar. :firejump:
[/EDIT]

Denn Exceptions in irgendeinem Thread (auch dem MainThread), welche bis zum System durchrauschen, da beendet dann Windows gleich den kompletten Prozess, drum fängt TThread sowas ab.

AJ_Oldendorf 12. Feb 2021 12:38

AW: Freigabe Thread
 
auch im Debugger nicht aber liegt wahrscheinlich daran, dass ich einige Exceptions raus genommen habe (absichtlich) und da ist die bestimmt mit dabei

TiGü 12. Feb 2021 13:13

AW: Freigabe Thread
 
Zitat:

Zitat von DieDolly (Beitrag 1482844)
Zitat:

TThread.ForceQueue(nil,
Alles ein Glücksspiel. Ich würde prinzipiell nicht im Kontext des Threads freigeben, egal wie, macht man nicht.
Ich schneide mir ja auch nicht den Ast ab, auf dem ich sitze.

Von außen erzeugen und von außen freigeben.

Kopiere dir mein Beispiel und debugge es.
An der Stelle sind wir im MainThread-Kontext und forcieren nochmal das abarbeiten weiterer Messages, indem wir das Freigeben per ForceQueue nochmal auf Reisen schicken.

jziersch 12. Feb 2021 13:28

AW: Freigabe Thread
 
Zitat:

Ich create einen Thread mit FreeOnTerminate=False, der tut irgendwas, wird fertig und wird dann wieder gefreed.
Hast Du schon darüber nachgedacht stattdessen einfach einen TTask zu nehmen?

Code:
     paramdata := TEtwas.Create; // Parameter für den Task. Werden am Ende gelöscht.

     TTask.Run( procedure
     var local : Integer;
     begin
       
        try
            // To was ...


            // Uebergib das Ergebnis an das Programm
            TThread.Synchronize( nil,
              procedure
              begin
                Memo1.Text := 'Bin Fertig';
              end
               );
        finally
           paramdata.Free;
        end;
     end
   );

AWeber 12. Feb 2021 17:37

AW: Freigabe Thread
 
Hallo,

Was ich in deinem Originalcode für kritisch halte ist die Sequenz aus .Terminate; gefolgt von .free; da der Aufruf von Terminate lediglich das Ende des Threads einleitet aber bei der Rückkehr der Thread ggf. noch läuft und du einen laufenden Thread frei gibst. Ich verwende steht's die Sequenz aus.

.Terminate;
.WaitFor;
.free;

Auch würde ich davon abraten den Thread direkt im .Execute freizugeben. Was du mit deinem OnReady Ereignis tust.

André

Mavarik 12. Feb 2021 17:37

AW: Freigabe Thread
 
Ich würde in so einem Fall auch TTask.Run nehmen...
es sei den,
Ich will nicht den Overhead und erzeuge mir vorab einen Thread. Diese kille ich am Ende des Programms.
Der Thread wartet auch einen TEvent und verbraucht keine Taktzyklen, starten aber in wenigen Pico Sekunden.
Dafür erzeuge ich ggf. eine Queue in die ich neue Werte schreibe und der Thread startet, wenn das passiert ist.
Oder halt nur einen Wert. Also Werte an Thread übergeben, SetEvent aufrufen fertig. Wenn der Thread die Arbeit erledigt hat,
legt er sich wieder schlafen...

Oder ich nutze FreeOnTerminate und gebe dem Thread ein Notifyer Interface mit, dass dann die Werte halten kann, wenn der Thread schon
lange tot ist.

Zum Beispiel so:
Delphi-Quellcode:
program Project13;

{$APPTYPE CONSOLE}

uses
  System.Classes
, System.SysUtils
, Windows
;

Type
  INotify = Interface
    ['{27E27AAC-63A6-4289-8B58-BCD4039AAE6C}']
    Procedure Ready(Value : Integer);
  End;

  TNotify = Class(TInterfacedObject,INotify)
    strict private
      Procedure Ready(aValue : Integer);
    private
      fValue  : Integer;
      fOnReady : TProc<Integer>;
    public
      Constructor Create(aProc : TProc<Integer>);
      Destructor Destroy;override;
  end;

  TWorker = Class(TThread)
    Protected
      Procedure Execute;override;
    private
      fNotify : INotify;
    public
      Constructor Create(aNotify : INotify);
  End;


{ TNotify }

constructor TNotify.Create(aProc: TProc<Integer>);
begin
  Inherited Create;

  fOnReady := aProc;
end;

destructor TNotify.Destroy;
begin
  fOnReady(fValue);

  inherited;
end;

procedure TNotify.Ready(aValue: Integer);
begin
  fValue := aValue;
end;

{ TWorker }

constructor TWorker.Create(aNotify: INotify);
begin
  fNotify := aNotify;

  FreeOnTerminate := true;

  inherited Create(false);
end;

procedure TWorker.Execute;
begin
  Sleep(5000);
  fNotify.Ready(42);
end;

var
  Worker : TWorker;
begin
  try
    Worker := TWorker.Create(TNotify.Create(Procedure (aValue : Integer)
      begin // Still Thread Context
       Writeln(aValue,' MainThread:',Windows.GetCurrentThreadId() = System.MainThreadID);
      end));
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.
Natürlich kann ich daraus auch einen INotify<T> und TWorker<T> machen und das dann für alle möglichen Typen verwenden.
In diesem Fall, würde ich den Execute Teil, ggf. Überschreiben.

Mavarik :coder:

AJ_Oldendorf 15. Feb 2021 14:01

AW: Freigabe Thread
 
TTask habe ich ehrlich gesagt noch nie genutzt. Müsste ich mir mal angucken.
Das Beispiel von Mavarik sieht mehr irgendwie zu "viel" aus. Da ist der einfache Thread mit seinem Create/Destroy und Execute für mich übersichtlicher, sicherlich aber Ansichtssache.

DieDolly 15. Feb 2021 14:07

AW: Freigabe Thread
 
Zitat:

Das Beispiel von Mavarik sieht mehr irgendwie zu "viel" aus.
Für den normalen Hausgebrauch definitiv viel zu viel. Da kann man quasi alles von wegschmeißen.

Bleib bei deinem Thread der jetzt funktioniert und gut.
Ansonsten sieht das mit dem TTask abgespeckt so aus. Dein Ready-Event packst du am Ende wie bei deinem Thread selber rein
Delphi-Quellcode:
 TTask.Run(
  procedure
  begin
   // Code
  end);

Mavarik 16. Feb 2021 16:45

AW: Freigabe Thread
 
Zitat:

Zitat von AJ_Oldendorf (Beitrag 1483014)
Das Beispiel von Mavarik sieht mehr irgendwie zu "viel" aus. Da ist der einfache Thread mit seinem Create/Destroy und Execute für mich übersichtlicher, sicherlich aber Ansichtssache.

Naja mehr als ein Thread Create/Destroy ist das ja auch nicht - eigentlich noch viel zu einfach für den Hausgebrauch!

Zitat:

Zitat von DieDolly (Beitrag 1483015)
Zitat:

Das Beispiel von Mavarik sieht mehr irgendwie zu "viel" aus.
Für den normalen Hausgebrauch definitiv viel zu viel. Da kann man quasi alles von wegschmeißen.

Naja, eigentlich kann man davon NIX wegschmeißen. Das ist einfach ein super simple Methode um eine Benachrichtigung
zu erhalten, wenn der Thread fertig ist.

Normalerweise mache ich das noch aufwendiger!

Folgendes Beispiel:

In einem Form soll eine Aufgabe im Hintergrund ausgeführt werden...

Klar kann man das als "Schön-Wetter-Code" schreiben mit:

Delphi-Quellcode:
Procedure TForm1.Button1OnClick(Sender : TObject);
begin
  Button1.Enabled := false;
 
  TTask.Run(Procedure
    begin
      Sleep(5000); // of was richtiges.
      TThread.Queue(Nil,Procedure
        begin
          Edit1.Text := 'Thread ist fertig und hat: 42 ermittelt');
          Button1.Enabled := true;
        end);    
    end);
end;
1. Was ist, wenn der Sleep eine Abfrage auf einen WebServer ist, der nicht wiederkommt oder ein Timeout hat von 5 Min.
2. Was ist, wenn das eine lange SQL-Abfrage ist die einfach ewig dauert?

Problem - der Button, geht nicht wieder auf Enabled... Toll..
Was aber wenn der User das Fester bereits wieder geschlossen hat?

Peng Exeption und die fängt auch erstmal keine ab... Man kann natürlich da noch ein try except drum setzen...

OK - Dann das Beispiel mit einem eigenen TThread...
Was ist wenn der Thread im Execute etwas aufruft, das nicht wieder kommt und damit steht?
Richtig, ein TThread.Destroy signalisiert Terminate aber das wir leider nicht ausgeführt.
Wenn also das Thread im FormOnDestroy oder wo auch immer freigegeben werden soll, bleibt das Fenster offen und die Anwendung steht.

Ich brauche also wenigstens ein Handle, das OutOfScope geht, wenn ich das Form schließe. Das OutOfScope darf nicht versuchen den Thread frei zu geben, sonst steht die Anwendung wieder.
Ich nehme das "nur" als signal, um den OnFinish-Class back mitzuteilen, dass er nicht mehr aufs Form zugreifen darf.

Und der Thread? - Naja, der lebt als tote Instance solange, bis die Routine im Execute mal zurück kommt oder das Programm beendet wird.
Das kann ich auch 10x versuchen... Die wenigen bytes stören mich nicht. Leider gibt es - außer mit einem OutOfProcess Server keine andere Möglichkeit den Thread zu zerstören.
Aber ein OutOfProcess Thread wäre dann hierfür vielleicht doch etwas "zu viel"...


Mavarik :coder:

himitsu 16. Feb 2021 17:52

AW: Freigabe Thread
 
Zitat:

Was aber wenn der User das Fester bereits wieder geschlossen hat?
Einfach: Am Anfang im Queue nochmal prüfen, ob das Fenster noch da ist (z.B. über Screen.Forms das Self prüfen), bevor darauf zugegriffen wird.
Während des Queue kann das Fenster dann anschließend nicht mehr verschwinden. (außer man hat ein Application.ProgressMessage, einen Dialog oder Ähnliches darin)


Bei Dateioperationen auf eine Netzwerkfreigabe, da kann es auch schonmal ewig hängen.
Da kann man entweder Asynchron arbeiten, oder auch von außerhalb via MSDN-Library durchsuchenCancelIo abbrechen.

Auch viele Netwerkkomponenten und auch DB-Komponenten haben eine Funktion um von außerhalb Operationen in einem anderen Thread abzubrechen.


Falls es keinen Timeout gibt, oder der nicht immer funktioniert, kann man z.B. beim Schließen des Fensters dennoch einen Abbruch einleiten.
Den Thread selbst aber hart abzuschießen (MSDN-Library durchsuchenTerminateThread) ist aber niemals eine Lösung, denn wenn der z.B. grade dabei ist sich beim Speichermanager (FastMM) Seicher zu holen oder freizugeben und da grade in dem Moment eine CriticalSection gesperrt ist, kann man sich einen schönen Deadlock in anderen Threads einfangen.

Mavarik 16. Feb 2021 19:17

AW: Freigabe Thread
 
Zitat:

Zitat von himitsu (Beitrag 1483137)
Einfach: Am Anfang im Queue nochmal prüfen, ob das Fenster noch da ist (z.B. über Screen.Forms das Self prüfen), bevor darauf zugegriffen wird.
Während des Queue kann das Fenster dann anschließend nicht mehr verschwinden. (außer man hat ein Application.ProgressMessage, einen Dialog oder Ähnliches darin)

Ja ok, man kann die Self Instance in der Forms-Liste (im queue) suche... Darüber habe ich in der Tat noch nicht nachgedacht... Nette Idee!
Weil ein Assigned(Self) geht logischerweise nicht.

Zitat:

Zitat von himitsu (Beitrag 1483137)
Falls es keinen Timeout gibt, oder der nicht immer funktioniert, kann man z.B. beim Schließen des Fensters dennoch einen Abbruch einleiten.

Wenn der Code im Execute zu 100% unter meiner Kontrolle ist - ok, dann kann ich auf Terminated abtesten. Das ist einfach.

Zitat:

Zitat von himitsu (Beitrag 1483137)
Den Thread selbst aber hart abzuschießen (MSDN-Library durchsuchenTerminateThread) ist aber niemals eine Lösung

Genau, deshalb meine Lösung. Wenn ich das wirklich braucht, ist der Overhead klein und für die harten Fälle dann doch einen neuen Prozess erzeugen, der
sein eigenes Memory Management hat und ggf. den Thread killt und den Speicher aufräumt.

Mavarik :coder:


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:47 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