TThread terminate in FormClose (gelöst)

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
wolf_z
Beiträge: 88
Registriert: Mo 31. Aug 2009, 09:31

TThread terminate in FormClose (gelöst)

Beitrag von wolf_z »

Hallo allerseits,

ich habe ein merkwürdiges Problem mit Threads. Ich habe mehrere Threads laufen, die ich ordentlich terminieren möchte, wenn ich das Formular schließe. Ich habe meistens kein Problem, die Threads zu schließen wenn ich das vorher manuell mache (im 'onClick' eines Buttons), jedenfalls nicht bei weniger als 50 Threads. Wenn ich aber das gleiche in 'FormClose' versuche, gibt es eine Exception, so dass beim Schließen eine Fehlermeldung (irgendwas mit 'notifiern') erscheint.

Meiner Meinung liegt das Problem daran, dass bei einem terminate mit 'FreeOnTerminate' das destroy noch eine zeitlang auf sich warten läßt. Aber irgendwie wird die Abarbeitungsschlange für MyThread.destroy im FormClose nicht ordentlich abgearbeitet.

Ein weiteres Problem ist, dass ich nicht vorher feststellen kann, wieviele Threads mein System verträgt. Das ist z.B. sehr unterschiedlich je nach Betriebssystem. Bei Windows (4GB Arbeitspeicher) kann ich durchaus 1000 Threads setzen, die auch alle richtig arbeiten, aber wehe, ich versuche einen davon dann zu terminieren, dann hängt es sich auf. Beim Mac (2 GB) kann ich höchstens 60 Threads setzen, dann macht es beim Erzeugen Zong (Programmabsturz). Wieviel da geht, würde ich gerne vorher irgendwie rauskriegen, so dass ich gar nicht erst zuviele Threads erzeuge.

Je mehr Threads ich habe, umso eher die Wahrscheinlichkeit, dass die Terminierung auch nur eines Threads das System einfriert. Hier die relevanten Code-Sequenzen:

Code: Alles auswählen

procedure TListThread.Execute;
var res: longint; i:integer; S:string;
begin
  while not Terminated do
     begin
     res := basiceventWaitFor(10, _execEvent);
     if res = wrSignaled then
        try
          basiceventResetEvent(_execEvent);
 
          ...... do something ........
 
        finally
          basiceventSetEvent(_finishEvent);
        end
     else
        try
          // wait for termination
          res :=  basiceventWaitFor(10, _killEvent);
          if res = wrSignaled then
             begin
             basiceventResetEvent(_killEvent);
             terminate;
             break;  
             end
          else
             sleep(tsleep);     // tsleep = 1
        finally
          // ?
        end;
     if FSuspend then
        suspend; 
     end;
  FFinished := true;
end;

Mit der folgenden Funktion des Thread-Containers wird die Anzahl der Threads immer
auf FQuantity gesetzt. 'FQuantity = 0' bedeutet, alle Threads zu terminieren.

Code: Alles auswählen

function TThreadContainer.count:integer;
var list:TList;
begin
  Result := 0;
  list:= FThreadList.Locklist;
  try
    Result := list.count;
  finally
    FThreadList.Unlocklist;
  end;
end;  
 
procedure TThreadContainer.terminate;
var i:integer; list: TList; aThread:TListThread;
begin
 
  while (count > FQuantity) do
     begin
     with FThreadList.LockList do
        try
          aThread := TListThread(list[0]);
        finally
          FThreadList.Unlocklist;
        end;
     // terminate thread
     with aThread do
        begin
        FreeOnTerminate := true; 
        // Terminate; // dangerous! You can have an 'enterCriticalSection' after
        // 'terminate' and then you will get stack problems. Use a '_killEvent' instead:
        FFinished := false;
        basiceventSetEvent(_killEvent);
        // while not terminated do sleep(tsleep); // unsure! don't know why!
        // waitfor; // does not work here (Unix?);
        while not FFinished do sleep(tsleep); // this works pretty good!
        FThreadList.remove(aThread);
        // freeOnTerminate: thread is not yet completely destroyed from here
        // aThread.destroy;  // or freeAndNil: ---> does not work!
        // aThread := nil;
        end;
     end;
 
end;
Hat jemand Erfahrung damit, wie man parallele FPC-Threads sauber beendet und zwar cross-compatibel? Ich finde immer nur Lösungen, die entweder nur für Windows oder für Linux/Mac funktionieren. Bevor jmd. fragt, wozu ich 1000 Threads brauche? :mrgreen: Es geht nicht um ein konkretes Tread-Problem, ich möchte nur die Möglichkeiten von parallelen Threads auf allen drei Betriebssystemen untersuchen.
Zuletzt geändert von wolf_z am Sa 14. Nov 2009, 16:55, insgesamt 1-mal geändert.

Patito
Beiträge: 203
Registriert: Di 22. Sep 2009, 13:08
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit

Re: TThread terminate in FormClose

Beitrag von Patito »

wolf_z hat geschrieben:Hallo allerseits,
Hallo. Mit Threads hatte ich noch wenig zu tun, aber trotzdem mal ein paar Kommentare/Fragen von mir: 

Code: Alles auswählen

procedure TThreadContainer.terminate;
var i:integer; list: TList; aThread:TListThread;
begin
  while (count > FQuantity) do
  begin
     with FThreadList.LockList do
        try
          aThread := TListThread(list[0]);
        finally
          FThreadList.Unlocklist;
        end;
     // terminate thread
     with aThread do
        begin
        FreeOnTerminate := true; 
        // Terminate; // dangerous! You can have an 'enterCriticalSection' after
        // 'terminate' and then you will get stack problems. Use a '_killEvent' instead:
        FFinished := false;
        basiceventSetEvent(_killEvent);
Hier killst Du doch den Thread?! Ist der damit dank FreeOnTerminate und bei passendem Timing hier nicht schon komplett entsorgt?

Code: Alles auswählen

// while not terminated do sleep(tsleep); // unsure! don't know why!
        // waitfor; // does not work here (Unix?);
        while not FFinished do sleep(tsleep); // this works pretty good!
Wenn ich so einen Source-Kommentar ("this works pretty good") in Multithreading-Code sehe entsorge ich immer reflexhaft sofort die komplette Unit. Hm. War der Thread hier nicht schon entsorgt? Ein Zugriff auf FFinished sollte hier doch vernünftigerweise zum Crash führen?!?

Code: Alles auswählen

FThreadList.remove(aThread);
        // freeOnTerminate: thread is not yet completely destroyed from here
        // aThread.destroy;  // or freeAndNil: ---> does not work!
        // aThread := nil;
        end;
     end;
end;

wolf_z
Beiträge: 88
Registriert: Mo 31. Aug 2009, 09:31

Re: TThread terminate in FormClose

Beitrag von wolf_z »

"basiceventSetEvent(_killEvent);" ist nur eine Botschaft, die an den Thread geschickt wird. In "execute" wird diese Botschaft abgefangen:

Code: Alles auswählen

procedure TListThread.Execute;
....
 
 // wait for termination
          res :=  basiceventWaitFor(10, _killEvent);
          if res = wrSignaled then
             begin
             basiceventResetEvent(_killEvent);
             terminate;
             break;  
             end
Dort wird dann der Thread terminiert und dann die Schleife beendet. Geht man dann zum Auslöser zurück, müsste man dort auf die Beendigung des Threads warten. Nach Schulbuch macht man das mit "aThread.waitFor". Aber das funktioniert vor allem unter Unix nicht. Es bleibt dort hängen. Bis zu dem Zeitpunkt kann man aber noch alle Variablen des Threads abfragen. Das kann man auch mit dem Debugger nachprüfen. Man kann aber auch auf das 'freeOnTerminate' verzichten und dann den Thread "zu Fuß" destroyen. Das hab ich in dem Beispiel ausgeklammert, weil dann das Verhalten noch schlimmer ist.

Aber das Problem ist wirklich, dass schon ein 'terminated' gesetzt wird, ohne dass der Thread schon 'destroyed' ist. In 'OnClose' des Formulars hat man dann nicht abgemeldete Objekte und es kommt dann zu Exceptions. Übrigens auch, wenn man viele Threads schnell hintereinander 'terminiert'.

Ich bräuchte etwas, das mir definitiv sagt, das der Thread 'destroyed' ist. Das werde ich mal morgen versuchen mit einem "basiceventSetEvent(_destroyEvent);", das ich in 'destroy' des Threads einbaue oder ähnliches. Das geht vielleicht, wenn man die Treads von Hand - also per Formularbutton - killt. Dann hat das Programm ja genügend Zeit, die Threads zu 'destroyen'. Aber im 'Formclose' steht gar nicht mehr genügend Zeit zur Verfügung, auf das destroy zu warten, weil das total asynchron läuft. Kann auch sein, dass irgendwie der Stack durcheinandergerät.

ABer nochmal zusammenfassend. An der Stelle, wo ich das 'terminated' oder alternativ das 'finished' überprüfe, gibt es das ThreadObjekt noch.

wolf_z
Beiträge: 88
Registriert: Mo 31. Aug 2009, 09:31

Re: TThread terminate in FormClose

Beitrag von wolf_z »

"basiceventSetEvent(_killEvent);" ist nur eine Botschaft, die an den Thread geschickt wird. In "execute" wird diese Botschaft abgefangen:

Code: Alles auswählen

procedure TListThread.Execute;
....
 
 // wait for termination
          res :=  basiceventWaitFor(10, _killEvent);
          if res = wrSignaled then
             begin
             basiceventResetEvent(_killEvent);
             terminate;
             break;  
             end
Dort wird dann der Thread terminiert und dann die Schleife beendet. Geht man dann zum Auslöser zurück, müsste man dort auf die Beendigung des Threads warten. Nach Schulbuch macht man das mit "aThread.waitFor". Aber das funktioniert vor allem unter Unix nicht. Es bleibt dort hängen. Bis zu dem Zeitpunkt kann man aber noch alle Variablen des Threads abfragen. Das kann man auch mit dem Debugger nachprüfen. Man kann aber auch auf das 'freeOnTerminate' verzichten und dann den Thread "zu Fuß" destroyen. Das hab ich in dem Beispiel ausgeklammert, weil dann das Verhalten noch schlimmer ist.

Aber das Problem ist wirklich, dass schon ein 'terminated' gesetzt wird, ohne dass der Thread schon 'destroyed' ist. Das alles wäre kein Problem, wenn ich mit 'waitFor' arbeiten könnte. Aber das funktioniert halt in Unix (also Linux und Mac) nicht. Deshalb meine Formulierung mit 'pretty good', weil mir selbst klar ist, das es sich um eine "quick&dirty"-Lösung handelt. Schulbuch wäre halt 'waitFor' aber geht nicht überall! Das könnte vielleicht damit zusammenhängen, dass in Unix die Threads ja Prozesse sind und ganz anders verarbeitet werden als unter Windows, das ja Botschafts-orientiert ist. Das kann man auch vermuten, wenn man sich die Implementierung von 'waitFor' anschaut.

In 'OnClose' des Formulars hat man dann nicht abgemeldete Objekte und es kommt dann zu Exceptions. Übrigens auch, wenn man viele Threads schnell hintereinander 'terminiert'.

Ich bräuchte etwas, das mir definitiv sagt, das der Thread 'destroyed' ist. Das werde ich mal morgen versuchen mit einem "basiceventSetEvent(_destroyEvent);", das ich in 'destroy' des Threads einbaue und auf das ich dann im ThreadContainer warte oder ähnliches. Das geht vielleicht, wenn man die Treads von Hand - also per Formularbutton - killt. Dann hat das Programm ja genügend Zeit, die Threads zu 'destroyen'. Aber im 'Formclose' steht gar nicht mehr genügend Zeit zur Verfügung, auf das destroy zu warten, weil das total asynchron läuft. Und da funktioniert auch der Empfang von Botschaften nicht mehr richtig. Kann aber auch sein, dass irgendwie der Stack durcheinandergerät.

ABer nochmal zusammenfassend. An der Stelle, wo ich das 'terminated' oder alternativ das 'finished' überprüfe, gibt es das ThreadObjekt noch.

Patito
Beiträge: 203
Registriert: Di 22. Sep 2009, 13:08
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit

Re: TThread terminate in FormClose

Beitrag von Patito »

wolf_z hat geschrieben:"basiceventSetEvent(_killEvent);" ist nur eine Botschaft, die an den Thread geschickt wird. In "execute" wird diese Botschaft abgefangen:
Ja, ist mir soweit klar. Ich dachte jetzt eigentlich, dass Deine Thread die ganze Zeit läuft und die ganze Zeit seine Execute-Schleife ausführt. -> Sobald das Event gesetzt wird kann er sofort seine Execute-Schleife beenden und sich vernichten...?!?

wolf_z
Beiträge: 88
Registriert: Mo 31. Aug 2009, 09:31

Re: TThread terminate in FormClose

Beitrag von wolf_z »

Patito hat geschrieben: Ja, ist mir soweit klar. Ich dachte jetzt eigentlich, dass Deine Thread die ganze Zeit läuft und die ganze Zeit seine Execute-Schleife ausführt. -> Sobald das Event gesetzt wird kann er sofort seine Execute-Schleife beenden und sich vernichten...?!?
Neee, wenn ich den Thread innerhalb execute 'destroye', steigt das Programm auch aus. Ich habe übrigens bestimmt alle Alternativen dieser Art schon ausprobiert :cry: Ich habe aber mal ein kleines Programm gebastelt, mit dem man das Problem nachvollziehen kann (Vorsicht: Suchgefahr! :wink: ):

Dazu ziehe man sich drei Buttons auf ein Formular ('btn_newLife', 'btn_fire', 'btn_terminate'), eine TMemo-Komponente ('mm_world') und einen Timer ('timer1', enabled, Intervall:=1000), benenne das Formular als 'frm_god' und fülle es mit folgendem Code:

Code: Alles auswählen

unit formworld;
 
{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}
 
interface
 
uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ExtCtrls, Spin, Buttons, ComCtrls, uthreadworld, math;
 
type
 
  { Tfrm_god }
 
  Tfrm_god = class(TForm)
    // Panel1:TPanel;
    // Label1:TLabel;
    btn_newLife:TButton;
    btn_fire:TButton;
    btn_terminate:TButton;
    mm_world: TMemo;
    // GroupBox1: TGroupBox;
    Timer1: TTimer;
    procedure btn_fireClick(Sender: TObject);
    procedure btn_newLifeClick(Sender: TObject);
    procedure btn_terminateClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    FWorld: TThreadWorld;
  public
    { public declarations }
  end; 
 
var
  frm_god: Tfrm_god;
 
implementation
 
{ Tfrm_god }
 
procedure Tfrm_god.FormCreate(Sender: TObject);
begin
  FWorld := TThreadWorld.create;
end;
 
procedure Tfrm_god.btn_newLifeClick(Sender: TObject);
begin
  FWorld.newLife;
end;
 
procedure Tfrm_god.btn_terminateClick(Sender: TObject);
begin
  FWorld.terminate;
end;
 
procedure Tfrm_god.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  FWorld.terminate;
end;
 
procedure Tfrm_god.btn_fireClick(Sender: TObject);
begin
  FWorld.fire;
end;
 
procedure Tfrm_god.FormDestroy(Sender: TObject);
begin
  FWorld.free;
end;
 
procedure Tfrm_god.Timer1Timer(Sender: TObject);
begin
  mm_World.lines.text := FWorld.View;
end;
 
initialization
  {$I formworld.lrs}
  randomize;
end.
Mit dem Button 'new life' werden beliebig viele life-threads erzeugt, die im Sekundentakt hochzählen. Der individuelle Zählerstand der Threads wird in dem Memo 'World' angezeigt. Mit dem Button 'fire' kann man einen zufällig ausgewählten Thread auf 'Null' zurücksetzen. Mit dem Button 'terminate' zerstört man alle Threads. Achtung: Da jeder Thread im Sekundentakt hochzählt, kann das Terminieren länger dauern, z.B. 10 Threads beenden dauert ungefähr 10 Sekunden.

Hier jetzt die thread-Unit (Achtung: unter Projekt/Compileroptions/andere eintragen: '-dUseCThreads'):

Code: Alles auswählen

unit uthreadworld;
 
{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}
 
interface
 
uses
  {$IFDEF UNIX}
    {$IFDEF UseCThreads}
    cthreads,            // compiler option "-dUseCThreads" needed under "other"
    {$ENDIF}
    DateUtils,
  {$ENDIF}
  SyncObjs,  LCLIntf, Classes, Sysutils, Dialogs, Math
  {, Forms // Threads should not use unit forms}
  ;
 
Const
 
  wrSignaled = 0;
  wrTimeout  = 1;
  wrAbandoned= 2;
  wrError    = 3;
 
  tsleep = 1;
 
type
 
  { TLifeThread }
 
  // Simple thread model for parallel running
  TLifeThread = class(TThread)
  private
  public
    Value:extended;
    _fireEvent:pEventState;
    constructor create; overload;
    destructor destroy; override;
    procedure Execute; override;
  end;
 
  { TThreadWorld }
 
  // Simple container for parallel threads
  TThreadWorld = class(TList)
  private
    FView:TStringList;
    locked:boolean;
    function getView:string;
    procedure threadTerminated(Sender: TObject);
  public
    constructor create;
    destructor destroy;
    procedure fire;
    procedure terminate;
    procedure newLife;
    property View:string read getView;
  end;
 
implementation
 
{ TLifeThread }
 
constructor TLifeThread.create;
begin
  inherited create(false {suspended});
  FreeOnTerminate := false;
  _fireEvent := BasicEventCreate(nil,false,false,'');
end;
 
destructor TLifeThread.destroy;
begin
  basiceventdestroy(_fireEvent);
  inherited;
end;
 
procedure TLifeThread.Execute;
var res:longint;
begin
  while not Terminated do
     begin
     res := basiceventWaitFor(1000, _fireEvent);
     if res = wrSignaled then
        begin
        basiceventResetEvent(_fireEvent);
        Value := 0;
        end
     else
        Value := Value + 1;
     // sleep(1000);
     end;
end;
 
{ TThreadWorld }
 
constructor TThreadWorld.create;
begin
  inherited;
  FView := TStringList.create;
end;
 
destructor TThreadWorld.destroy;
begin
  terminate;
  FView.free;
  inherited;
end;
 
function TThreadWorld.getView:string;
var i:integer;
begin
  if locked then
     exit;
  locked := true;
  try
    FView.clear;
    For i := 0 to self.count - 1 do
       FView.add(floatToStr(TLifeThread(self[i]).Value));
    Result := FView.text;
  finally
    locked := false;
  end;
end;
 
procedure TThreadWorld.threadTerminated(Sender: TObject);
var ix:integer;
begin
  ix := indexOf(Sender);
  delete(ix);
end;
 
procedure TThreadWorld.terminate;
var i:integer; aThread:TLifeThread;
begin
  while locked do sleep(1);
  try
    locked := true;
    for i := count - 1 downto 0 do
       begin
       aThread := TLifeThread(self[i]);
       aThread.freeOnTerminate := false;
       aThread.terminate;
       aThread.waitFor;  // works on Windows,
                         // not on Mac,
                         // not on Linux
       // while not aThread.terminated do sleep(1); // works on Windows,
                                                 // not on Mac
                                                 // not on Linux
       freeAndNil(aThread);
       end;
  finally
    locked := false;
  end;
end;
 
procedure TThreadWorld.newLife;
var newLife:TLifeThread;
begin
  newLife := TLifeThread.Create;
  newLife.onTerminate := threadTerminated;
  add(newLife);
end;
 
procedure TThreadWorld.fire;
var ix:integer;
begin
  ix := random(count);
  basiceventSetEvent(TLifeThread(self[ix])._fireEvent);
end;
 
end.

In der Methode "TThreadWorld.terminate" habe ich eingetragen, was unter den jeweiligen Betriebssystemen geht und was nicht. 'WaitFor' funktioniert unter Windows, aber nicht unter 'Unix'-Betriebssystemen, wie Linux und Mac. Der Grund ist wahrscheinlich, dass das Thread-Management unter Unix komplett anders funktioniert als unter Windows (läuft auf Basis von Prozessen, soviel ich weiß).

Die erste Frage wäre also, wie werden Threads unter Unix-Systemen korrekt beendet.

Ein weiteres Problem, dass sich danach stellt, ist, wie man vor dem Starten eines neuen Threads die maximal zulässige Anzahl von Threads ermittelt. Das würde mich auch noch sehr interessieren. Meine Erfahrungen sind unter Windows: bis zu 1000, unter Mac maximal 60. Es kann auch sein, dass selbst unter Windows das oben dargestellte Verfahren mit sehr vielen Threads nicht funktioniert. Hab ich jetzt bei diesem einfachen Beispiel nicht getestet.

Aber erstmal die grundsätzliche Frage: Wie kann man unter Unix in dem oben dargestellten Fall alle laufenden Threads mit einem Knopfdruck sicher beenden?

Patito
Beiträge: 203
Registriert: Di 22. Sep 2009, 13:08
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit

Re: TThread terminate in FormClose

Beitrag von Patito »

wolf_z hat geschrieben:
Patito hat geschrieben: Ja, ist mir soweit klar. Ich dachte jetzt eigentlich, dass Deine Thread die ganze Zeit läuft und die ganze Zeit seine Execute-Schleife ausführt. -> Sobald das Event gesetzt wird kann er sofort seine Execute-Schleife beenden und sich vernichten...?!?
Neee, wenn ich den Thread innerhalb execute 'destroye', steigt das Programm auch aus. Ich habe übrigens bestimmt alle Alternativen dieser Art schon ausprobiert :cry:
Nunja, soviel destroy brauchst Du doch gar nicht. Du hast FreeOnTerminate gesetzt und die Kill-Message gesendet. Dadurch kann sich der Thread jederzeit vernichten und Zugriffe auf FFinished sind jetzt Glückssache. (1.Problem)

:?: :!:

Dein Hauptproblem war aber wohl, dass Du im FormClose noch darauf warten willst, dass die Threads nicht noch irgendwo in ihrem _execEvent-Teil stecken. Deine Idee die Threads ein Stop-Event senden zu lassen und im FormClose dann darauf zu warten klang doch schon ganz richtig.
Eventuell wäre da eine Semaphore auch ganz gut. Für jeden Thread, wenn er die kill-message verarbeitet die Semaphore hochzählen, und das Formular wartet dann auf Semaphorenzähler = Threadanzahl.

:?: :!:

wolf_z
Beiträge: 88
Registriert: Mo 31. Aug 2009, 09:31

Re: TThread terminate in FormClose

Beitrag von wolf_z »

Na ja, die Überlegung, ein Event im 'destroy' des Threads auszuführen, war nicht so gut von mir. Denn dort muss das Event ja selbst zerstört werden.

Das mit dem _killEvent, der dann in 'execute' des threads ein 'terminate' mit anschließendem 'break' aus der Schleife auslöst, kann man machen und ist wahrscheinlich vor allem für komplexe Threads sicherer. Dann kann man im Container aber nur das 'terminated' abfragen. 'WaitFor' (Schulbuch) funktioniert dann nicht mehr. Und Abfragen des 'terminated' ist halt nicht threadsicher.

Das mit den Semaphoren versteh ich nicht ganz. Wenn ich ein solches Semaphore nach dem killEvent in der execute-Schleife setze bzw. hochzähle, dann habe ich es ja nach dem destroy nicht zur Verfügung. Damit kann ich immer noch nicht den Abschluss von 'terminate' identifizieren. Jetzt könnte ich für jeden Thread im Container einen eigenen Zähler anlegen das Hochzählen dann zwischen einem 'EnterCriticalSection und einem LeaveCriticalSection, der das Hochzählen vor Zugriffskonflikten durch aus Auswerten im Container schützt, absichern.

Das könnte gehen, ist aber sehr umständlich. Ich müsste ein Array aus Zählern im Container anlegen. Meinst Du das mit 'Semaphoren'? Weil, ich fand die Theorie zu Threads immer unanschaulicher als die Praxis :mrgreen: Deswegen kenne ich mich mit Semaphoren nicht so genau aus.

Ich könnte das jetzt mal versuchen, aber gibt es denn da nichts einfacheres? Es muss doch ein sicheres Verfahren für Unix geben (wie 'waitFor' unter Windows), mit dem man Threads in jeder Situation killen kann. Oder ist das tatsächlich so eine Wissenschaft?

Ach so, es gibt ja noch das onTerminated-Ereignis eines jeden Threads. Aber darin z.B. Flags oder Zähler setzen und die dann Abfragen führt zu jede Menge Zugriffskonflikten. Denn jeder Thread kann ja gleichzeitig auf diese Methode zugreifen. Und die auswertende while-Schleife, die ja den Zustand einer solchen Variable permanent feststellen soll, um auf das Ende zu warten, ebenfalls. Hab ich probiert, gibt großen Zong!

Ist das tasächlich alles so schwierig? Ich kann es mir einfach nicht vorstellen. Irgendwo da draußen im Universum muss es eine ganz einfache Lösung für das Problem geben :| (so wie unter Windows das waitFor)

creed steiger
Beiträge: 958
Registriert: Mo 11. Sep 2006, 22:56

Re: TThread terminate in FormClose

Beitrag von creed steiger »

Hilft dir das ein wenig weiter?
http://bugs.freepascal.org/view.php?id=13105" onclick="window.open(this.href);return false;

Patito
Beiträge: 203
Registriert: Di 22. Sep 2009, 13:08
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit

Re: TThread terminate in FormClose

Beitrag von Patito »

wolf_z hat geschrieben:Na ja, die Überlegung, ein Event im 'destroy' des Threads auszuführen, war nicht so gut von mir. Denn dort muss das Event ja selbst zerstört werden.
Du könntest doch die Events (oder die Semaphore) im Hauptthread erzeugen und beim Start den Threads das Handle oder den Namen des Synch-Objektes übergeben. Zuständig das Ding beim Betriebssystem wieder abzumelden ist dann der Hauptthread.
Das könnte gehen, ist aber sehr umständlich. Ich müsste ein Array aus Zählern im Container anlegen. Meinst Du das mit 'Semaphoren'? Weil, ich fand die Theorie zu Threads immer unanschaulicher als die Praxis :mrgreen: Deswegen kenne ich mich mit Semaphoren nicht so genau aus.
Semaphoren sind so eine Art threadsichere Zähler. Und so wie ich das von der Uni her kenne waren die eigentlich einer der wenigen Grundbausteine mit denen man die Thread-Synchronisierungen programmiert. Mir ist ein wenig schleierhaft warum die nicht von Anfang an in den SynchObj - Klassen von Delphi und FPC drin waren. Inzwischen sollte es bestimmt in irgendeiner Unit ein TSemaphore geben. Zur Not muß man das selbst emulieren mit einer CriticalSection und einem Integer.

wolf_z
Beiträge: 88
Registriert: Mo 31. Aug 2009, 09:31

Re: TThread terminate in FormClose

Beitrag von wolf_z »

@creed steiger
Danke! Genauso was habe ich gesucht für das Problem mit der maximalen Anzahl Threads. Irgendwie ist auch klar, dass das Problem nicht einfach zu lösen ist. Was mich nur wundert ist, dass sich Mac und Linux/Windows so stark unterscheiden. Z.B. beim Mac nicht mehr als 60 Threads? Aber es ist schon mal beruhigend, dass auch die Cracks das Problem nicht abschließend gelöst haben. Liegt also nicht an meiner Dummheit :mrgreen:

@Patito
Ja, genau sowas ähnliches ist mir heute Nacht auch noch eingefallen. Du gehst ja bei Deiner Diskussion von einem sogeannten "Hauptthread" aus. Und das ist glaube ich, der Knackpunkt. Man muss einen Manager-Thread haben! Das habe ich auch irgendwo gelesen, dass das ratsam ist, aber ich hatte bisher darauf bei meinen Lösungen verzichtet. Dort kann man dann im Gegensatz zum Hauptprogramm oder meinem bisherigen von TComponent abgeleiteten Container-Objekt beliebige Code-Sequenzen per CriticalSection schützen, z.B. auch das, was man in der 'onTerminate'-Ereignisbehandlung für einen Thread implementiert (hoffe ich jedenfalls). Dort könnte man nämlich durchaus einen Threadzähler unterbringen. Ansonsten aber irgendwo (also nicht unbedingt in 'onTerminate') das, was Du als Semaphore bezeichnest.

Danke erst mal bis hierhin. Hat mich glaube ich, in die richtige Richtung gebracht. Sobald ich dazu gekommen bin, das umzusetzen, baue ich das auch noch in das Beispiel oben ein und liefere das hier ab, falls sich nicht neue Problem ergeben.

Grüße
Wolfgang

wolf_z
Beiträge: 88
Registriert: Mo 31. Aug 2009, 09:31

Re: TThread terminate in FormClose

Beitrag von wolf_z »

Also ich hab jetzt alles probiert, aber ich krieg die Krätze! Ich kann machen was ich will, beim FormClose gibt es eine Exception im Application.terminate, nachdem alle Objekte sauber terminiert und zerstört wurden. Ich versteh es einfach nicht. Schaut man sich beim Auftreten der Exception im Aufruf-Stack die letzten Methoden an, so hängt er irgendwo nach einer Behandlung von CriticalSection, aber ich verwende gar keine CriticalSections mehr.

Vielleicht liegt es ja nur an meinen Lazaruseinstellungen.

Den gleichen Fehler bekomme ich übrigens, wenn ich unter Linux das Lazarusbeispiel: /examples/multithreading/multithreadingexample1.lpi starte. Das ist quasi eine threadgesteuerte Uhr. Der Fall ist insofern vergleichbar, als auch hier ein laufender Thread durch ein Schließen des Formulars beendet wird, allerdings ohne besondere Maßnahmen. Im Gegensatz zu meinem Programm oben erzeugt diese Uhr allerdings auch unter Windows bei mir eine Exception bei FormClose. Ich bekomme im Aufrufstack allerdings andere Informationen als bei meinem Programm (also keine Hinweise auf CriticalSection).

Komisch ist dann allerdings, dass sich im Lazarusbeispiel: /examples/multithreading/waitforunit1.lpi das Formular auch bei laufenden Threads eindeutig schließen lässt. Ich kann grundsätzlich dort keinen Unterschied sehen, was die Projekteinstellungen und die Verwendung von unit cthreads betrifft.

Es gibt übrigens noch ein Beispiel im Lazarus-Wiki, das meiner Meinung nach fehlerhaft ist (unten auf der Wiki-Seite).

http://wiki.lazarus.freepascal.org/Mana ... ads_System" onclick="window.open(this.href);return false;

Das Ding arbeitet mit 'waitFor' worauf ich jetzt aufgrund der Probleme mit der Cross-Kompatibilität komplett verzichte. Ich frage stattdessen das terminated eines Threads in einer while-Schleife ab. In dem Beispielprogramm funktioniert das waitfor ebenfalls nicht unter Linux, wie ich weiter oben schon festgestellt hatte. Die Exception zeigt sich im Debugger. Im frei laufenden Programm kommt es nur deshalb nicht zu einer Fehlermeldung, weil das Beispiel nicht besonders ausgebaut ist. Das gilt meiner Meinung auch für die genannten Lazarus-Examples. Dort werden überhaupt keine Maßnahmen getroffen, um die Threads sauber zu beenden bei einem FormClose. Je nach zeitlicher Charakteristik der Threads funktioniert es manchmal, manchmal aber auch nicht. Ob die Beispiele funktionieren ist meiner Meinung nach reine Glücksache.

Kann vielleicht jemand mal das Programm /examples/multithreading/multithreadingexample1.lpi unter Linux starten und mehrmals schließen und mir sagen, ob das Programm dabei eine Exception erzeugt? Unter Windows vielleicht auch. Bitte mehrfach starten und schließen, denn manchmal funktioniert es auch.

Ich sehe darin ein gravierendes Problem. Denn schließlich gehört es zu jeder Anwendung, dass man alle Objekte mit dem FormClose sauber schließen kann. Ich kann mir natürlich auch helfen, indem ich den Anwender auffordere, die Threads vor dem FormClose zu beenden, beispielweise mittels separatem Button. Dann habe ich überhaupt keine Probleme. Aber das wäre sehr unergonomisch.

Hat keiner ähnliche Probleme? Das muss doch schon vielen Leuten aufgefallen sein. Vor allem, weil die Lazarus-Beispiele ja auch nicht richtig gehen. Oder liegt es an meinen Lazarus-Parametern?

Hitman
Beiträge: 512
Registriert: Mo 25. Aug 2008, 18:17
OS, Lazarus, FPC: ArchLinux x86, WinVista x86-64, Lazarus 0.9.29, FPC 2.4.1
CPU-Target: x86
Wohnort: Chemnitz

Re: TThread terminate in FormClose

Beitrag von Hitman »

Gabs beim Thread nich ein .WaitFor oder sowas? Das müsste man dann halt unmittelbar nach dem Senden des Terminate Signals aufrufen, damit die weitere Code Ausführung so lange wartet, bis der Thread mit seiner Ausführung fertig ist.

wolf_z
Beiträge: 88
Registriert: Mo 31. Aug 2009, 09:31

Re: TThread terminate in FormClose

Beitrag von wolf_z »

@Hitman
Das hab ich oben schon mehrmals erwähnt. 'WaitFor' funktioniert nur auf Windows, nicht auf Unix (Linux/Mac). Wäre schön, wenn es sowas auch dort gäbe, aber das kneift sich wohl mit der grundsätzlich anderen Organisation von Threads (Prozesse).

Hitman
Beiträge: 512
Registriert: Mo 25. Aug 2008, 18:17
OS, Lazarus, FPC: ArchLinux x86, WinVista x86-64, Lazarus 0.9.29, FPC 2.4.1
CPU-Target: x86
Wohnort: Chemnitz

Re: TThread terminate in FormClose

Beitrag von Hitman »

Sorry, is mir dann beim Überfliegen wohl entgangen ;-)

Antworten