Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Probleme beim Warten auf 2 Threads (https://www.delphipraxis.net/81392-probleme-beim-warten-auf-2-threads.html)

RWarnecke 26. Nov 2006 13:07


Probleme beim Warten auf 2 Threads
 
Hallo,

ich probiere schon seit langem rum, wie ich auf 2 Threads warten kann. Ich habe jetzt folgende Ausgangssituation :

Delphi-Quellcode:
  TSearchCopy = class(TThread)
  private
    { Private-Deklarationen }
  public
    procedure Execute; override;
  end;

  TSearchDelete = class(TThread)
  private
    { Private-Deklarationen }
  public
    procedure Execute; override;
  end;
So erstelle ich meine Classen für die Threads.

Delphi-Quellcode:
procedure TMainForm.BtnCopyClick(Sender: TObject);
begin
.
.
.
      // Fenster anzeigen, wo er gerade sucht
      ProgressWindow.Visible := not ProgressWindow.Visible;
      SearchForCopy := TSearchCopy.Create(false);
      SearchForDelete := TSearchDelete.Create(false);
      // hier soll gewartet werden, bis die beiden Threads fertig sind
      // denn hier folgt weitere Sourcecode, der nach dem ausführen der Threads ausgeführt werden soll
.
.
.
end;
Und so starte ich es. In der Execute-Procedure wird nach Dateien gesucht. Dieses wird auch sauber angezeigt und funktioniert auch soweit sehr gut. Ich habe schon versucht, auf die beiden Threads mit :
Delphi-Quellcode:
repeat
  Application.ProcessMessages;
until (SearchForCopy.Terminate) and (SearchForDelete.Terminate);
zu warten, was aber leider nicht zum Erfolg führt. Wie kann ich das anderst machen ? Muss ich eventuell den Aufruf ändern oder das warten auf die Threads ?

karlkoch 26. Nov 2006 13:13

Re: Probleme beim Warten auf 2 Threads
 
Mit TThread.WaitFor kannst du auf einen Thread warten. Dieser Aufruf wartet solange, bis die Execute-Funktion des Threads beendet wird.
Wenn du währenddessen noch Dinge erledigen willst, dann sollten die Threads einfach z.B. einen Status setzen, den du abfragst.

karlkoch

RWarnecke 26. Nov 2006 13:20

Re: Probleme beim Warten auf 2 Threads
 
Wenn ich jetzt

Delphi-Quellcode:
SearchForCopy.WaitFor;
SearchForDelete.WaitFor;
Dort einsetze kommt nach kurzer Zeit die Fehlermeldung
Zitat:

Thread-Fehler: Das Handle ist ungültig(6)
. Desweitern wird nichtsmehr in dem Fenster von der Suche angezeigt.

Habe ich eventuell etwas falsch gemacht ?

Muetze1 26. Nov 2006 13:31

Re: Probleme beim Warten auf 2 Threads
 
Vermutlich wartet er im ersten Thread mit dem WaitFor bis dieser beendet ist. Und wenn dies eingetreten ist ruft er WaitFor vom zweiten Thread auf - der hatte aber die Wartezeit auf den ersten Thread genutzt und ist schon beendet.

Meine Vermutung...

RWarnecke 26. Nov 2006 13:46

Re: Probleme beim Warten auf 2 Threads
 
Ich habe das ganze jetzt mal im Einzelschritt ausgeführt. Wenn ich dann die Zeile
Delphi-Quellcode:
SearchForCopy.WaitFor;
ausführe, passiert auf dem Bildschirm für kurze Zeit garnichts und dann kommt der Fehler aus meinem letzten Beitrag. Gibt es noch andere Möglichkeiten auf Threads zu warten ?

thkerkmann 26. Nov 2006 13:50

Re: Probleme beim Warten auf 2 Threads
 
Hi,

setzt mal die property FreeOnTerminate deiner beiden Threads auf false.

Du musst dann natürlich nach den beiden WaitFor's die Threads selber freigeben

Dann sollte es gehen.

Gruss

RWarnecke 26. Nov 2006 14:00

Re: Probleme beim Warten auf 2 Threads
 
Erstmal ein dickes Danke an Thomas. Ich habe das ganze jetzt so gelöst :
Delphi-Quellcode:
      try
        SearchForCopy := TSearchCopy.Create(false);
        SearchForCopy.FreeOnTerminate := False;
        SearchForCopy.WaitFor;
        SearchForDelete := TSearchDelete.Create(false);
        SearchForDelete.FreeOnTerminate := False;
        SearchForDelete.WaitFor;
      finally
        SearchForCopy.Free;
        SearchForDelete.Free;
      end;
Kann mir jetzt noch jemand erklären, warum ich die TThreads selber freigeben muss und nicht über FreeOnTerminate ?

Jetzt habe ich nur noch das Problem, das mein Statusfenster zwar geöffnet wird aber nichts anzeigt von dem was meine beiden Threads macht. Wie löse ich das jetzt noch ?

thkerkmann 26. Nov 2006 14:24

Re: Probleme beim Warten auf 2 Threads
 
Zitat:

Zitat von RWarnecke
Erstmal ein dickes Danke an Thomas. Ich habe das ganze jetzt so gelöst :
Delphi-Quellcode:
      try
        SearchForCopy := TSearchCopy.Create(false);
        SearchForCopy.FreeOnTerminate := False;
        SearchForCopy.WaitFor;
        SearchForDelete := TSearchDelete.Create(false);
        SearchForDelete.FreeOnTerminate := False;
        SearchForDelete.WaitFor;
      finally
        SearchForCopy.Free;
        SearchForDelete.Free;
      end;
Kann mir jetzt noch jemand erklären, warum ich die TThreads selber freigeben muss und nicht über FreeOnTerminate ?

Jetzt habe ich nur noch das Problem, das mein Statusfenster zwar geöffnet wird aber nichts anzeigt von dem was meine beiden Threads macht. Wie löse ich das jetzt noch ?

Ich nochmal...

So hatte ich das nicht gemeint - hier laufen jetzt die zwei hintereinander ab.

Besser wäre

Delphi-Quellcode:
      try
        SearchForCopy := TSearchCopy.Create(true); // create suspended !
        SearchForCopy.FreeOnTerminate := False;
        SearchForDelete := TSearchDelete.Create(true); // create suspended !
        SearchForDelete.FreeOnTerminate := False;

        SearchForCopy.Resume; // thread laufen lassen
        SearchForDelete.Resume; // thread laufen lassen

        SearchForCopy.WaitFor; // warte auf Ende
        SearchForDelete.WaitFor; // warte auf Ende
      finally
        SearchForCopy.Free;
        SearchForDelete.Free;
      end;
So können die beiden Threads parallel laufen.

Das mit dem Freigeben hat folgenden Sinn:
Damit das WaitFor überhaupt funktioniert - darf sich der Thread am Ende nicht selber freigeben, sonst bekommst Du eine Zugriffsverletzung. Also musst Du das selber erledigen.

Gruss

RWarnecke 26. Nov 2006 14:36

Re: Probleme beim Warten auf 2 Threads
 
Danke Dir Thomas für die Erklärung. Ich habe aber jetzt immer noch das Problem, dass in meinem Fenster nichts angezeigt wird durch das WaitFor. Wie kann ich das jetzt noch bereinigen ?

thkerkmann 26. Nov 2006 14:57

Re: Probleme beim Warten auf 2 Threads
 
Ach ja.....

Dann war dein erster Ansatz doch nicht so schlecht....

Delphi-Quellcode:
repeat
  Application.ProcessMessages;
  sleep(1); // lass den Threads auch eine Chance zu arbeiten.
until (SearchForCopy.Terminated) and (SearchForDelete.Terminated); // achtung !! Terminated abfragen, nicht Terminate aufrufen.
Dies anstelle der WaitFor eingesetzt sollte helfen.

Allerdings auch hier wichtig - FreeOnTerminate auf false lassen und am Ende selber freigeben.

Gruss

Panthrax 26. Nov 2006 15:06

Re: Probleme beim Warten auf 2 Threads
 
Hallo Rolf.

Das WaitFor im Hauptthread der Anwendung einzubaue ist nicht sinnvoll. Damit konstruiert du genau das, was du zu vermeiden versuchst - das "Hängenbleiben" deiner Anwendung. Es bleibt also nichts anderes, als die Threads für sich separat arbeiten zu lassen und über eine Ereignisbehandung bzw. Signale zu kommunizieren, um damit eben nicht aktiv zu warten.

Ich möchte daher noch eine andere Läsung vorschlagen: Hast du dir die Delphi-Hilfe dazu einmal angesehen?
  • Warten, bis ein Thread vollständig ausgeführt ist
  • Warten, bis eine Aufgabe ausgeführt ist
Sehr angenehm kann man auch mit Hilfe des OnTermine-Ereignisses arbeiten. Die Delphi-Hilfe gibt folgende Information zum Ereignis OnTerminate:
Zitat:

Das Ereignis OnTerminate wird ausgelöst, sobald die Methode Execute des Threads zurückgekehrt ist, aber bevor der Thread freigegeben wird.
Gruß,
Panthrax.

RWarnecke 26. Nov 2006 15:18

Re: Probleme beim Warten auf 2 Threads
 
Irgendwie klappt das nicht. Denn einer beiden Threads gibt kein True zurück, deshalb bleibt es in der repeat-Schleife hängen.

Zitat:

Zitat von Panthrax
Ich möchte daher noch eine andere Läsung vorschlagen: Hast du dir die Delphi-Hilfe dazu einmal angesehen?
  • Warten, bis ein Thread vollständig ausgeführt ist
  • Warten, bis eine Aufgabe ausgeführt ist
Sehr angenehm kann man auch mit Hilfe des OnTermine-Ereignisses arbeiten. Die Delphi-Hilfe gibt folgende Information zum Ereignis OnTerminate:
Zitat:

Das Ereignis OnTerminate wird ausgelöst, sobald die Methode Execute des Threads zurückgekehrt ist, aber bevor der Thread freigegeben wird.

Ja, ich habe mir die Delphihilfe angesehen und wurde nicht richtig schlau draus. Deshalb habe ich diesen Weg gewählt. Könntest Du mir das bitte anhand meines Beispieles bitte erklären.

C.Schoch 26. Nov 2006 16:37

Re: Probleme beim Warten auf 2 Threads
 
Hi,
Du köntest auch per msgWaitForMultipleObjekts oder WaitForMultipleObjekts auf die Threads warten.

z.B. so:
Delphi-Quellcode:
 
Var
  dwWaitResult : DWORD;
  ThreadArray : array[0..1] of THandle; // Handle Array um auf Threads zu warten
begin
  SearchForCopy := TSearchCopy.Create(false);
  SearchForCopy.FreeOnTerminate := False;
  SearchForDelete := TSearchDelete.Create(false);
  SearchForDelete.FreeOnTerminate := False;
  ThreadArray[0] := SearchForCopy.Handle; // Handle Array füllen
  ThreadArray[1] := SearchForDelete;
  repeat
    // Auf Threads warten
    dwWaitResult := msgWaitforMultipleObjects(length(ThreadArray), ThreadArray, true, 100, QS_ALLINPUT);
    if dwWaitResult <> WAIT_OBJECT_0 then
    begin
       Application.ProcessMessages; // GUI aktualisieren
    end;  
  until dwWaitResult = WAIT_OBJECT_0; // keinen Threads mehr zum warten
end;

Panthrax 26. Nov 2006 16:52

Re: Probleme beim Warten auf 2 Threads
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Rolf.

Ich beschreibe dir hier das Konzept noch einmal etwas genauer. Im Anhang habe ich das Konzept in einem Beispielprojekt gezeigt.

Das Konzept für 2 Threads

Du willst auf die Beendigung zweier Threads warten. Dafür bietet sich das OnTerminate-Ereignis der Klasse TThread an. Die Ereignisbehandlungsroutine braucht die Informationen über
  • den Thread, der abgeschlossen ist
  • den anderen Thread, ob er noch läuft oder nicht.
Nun ist die Sache einfach. Aus der ersten Information kann geschlussfolgert werden, welcher Thread als beendet markiert werden kann. Das kann nicht mithilfe des Threads selbst passieren, da sich dieser nach Beendigung selbst freigibt (FreeOnTerminate = True). Aus der zweiten Information kann geschlussfolgert werden, ob beide Threads damit beendet sind.

Ein Beispiel

Im Beispiel habe ich die Klasse TSleepThread von TThread abgeleitet. Die Aufgabe ist eine bestimmte Zeit zu schlafen. Anschließend ist der Thread beendet. Wie lange geschlafen werden soll wird im Kontruktor angegeben. Ther Parameter CreateSuspended wird an den Kontruktor von TThread weitergereicht. Kurzfassung:
Delphi-Quellcode:
type
  TSleepThread = class(TThread)
  ...

constructor TSleepThread.Create(const Milliseconds: Cardinal; const CreateSuspended: Boolean);
Zum Formular habe ich 2 Variablen (Eigenschaften) hinzugefügt: FThread1 (Thread1) und FThread2 (Thread2). Beide sind von der Klasse TSleepThread. Diese Felder dienen nur zum Merken, ob ein Thread beendet ist (= nil) oder nicht (<> nil). Kurzfassung:
Delphi-Quellcode:
type
  TForm1 = class(TForm)
    private
    FThread1, FThread2: TSleepThread;

    published
    property Thread1: TSleepThread read FSleepThread1 write FSleepThread1;
    property Thread2: TSleepThread read FSleepThread2 write FSleepThread2;
  end;
Die Methode RunThreads initialisiert und startet die Threads. Kurfassung:
Delphi-Quellcode:
procedure RunThreads;
Der Kern liegt in der Methode DoTerminate. Diese wurde den erzeugten Threads dem Ereignis OnTerminate zugewiesen. Neben einer Ausgaberückmeldung setzt diese Methode die Merker Thread1 bzw. Thread2 auf Nil, wenn der zugehörige Thread beendet wurde. Wurden beide abgearbeitet, sind folglich beide Nil. Das ist also der Zustand, nach dem du gesucht hast. Kurzfassung:
Delphi-Quellcode:
procedure TForm1.DoTerminate(Sender: TObject);
begin
  { Merker setzten, dass ein Thread beendet wurde. }
  if Sender = Thread1 then Thread1:=nil else
  if Sender = Thread2 then Thread2:=nil;
  { Fall, dass beide Threads beendet wurden. }
  if not Assigned(Thread1) and not Assigned(Thread2) then...;
end;
Damit ergibt sich also nicht mehr das Problem, dass das Programm "hängt".

Viel Spaß beim ausprobieren mit dem Beispiel. Ich habe es so gestaltet, dass man die "Laufzeit" der Threads einfach in der Oberfläche einstellen kann.

Beispiel erstellt und getestet mit Delphi 7 Enterprise.

Gruß,
Panthrax.

[edit=Panthrax]Glatt den Anhang vergessen... Jetzt ist er dabei.[/edit]

RWarnecke 26. Nov 2006 18:45

Re: Probleme beim Warten auf 2 Threads
 
Ersteinmal ein dickes Danke an Christian, den sein Code funktioniert schon mal, wenn auch nicht ganz mit der Performance, als wenn ich nach dem Aufruf einfach die OnClick-Procedure beende.

Auch ein nochmaliges Danke an Dich Panthrax für Deine ausführliche Beschreibung. Verstehe ich das soweit richtig, dass der Code der nach der Ausführung der beiden Threads ausgeführt werden soll in der DoTerminate-Procedure stehen muss ? Wenn ja, wäre das nicht ganz Sinnvoll, da der Start von den beiden Threads in einer For-Schleife steht.

Panthrax 26. Nov 2006 19:31

Re: Probleme beim Warten auf 2 Threads
 
Schön, dass es dir gefällt.

Ob der Code, der nach Abschluss beider Threads ausgeführt werden soll, in der Ereignisbehandlungsroutine von OnTerminate stehen sollte oder nicht, kann ich nicht beurteilen. Fest steht, dass dort auf diesen Zustand reagiert werden kann. Sei es durch direktes Fortführen des Programmablaufs dort oder durch Anstoßen eines anderen Programmteils.

Ich würde sehr wahrscheinlich eine Routine schreiben, die diese sich anschließenden Aufgaben übernimmt. Um dir jedoch dabei helfen zu können, bäuchte man schon etwas genauere Angaben, als "steht in einer For-Schleife". Es bleibt die Frage: Was hat die For-Schleife damit zu tun?

Gruß,
Panthrax.

RWarnecke 27. Nov 2006 09:49

Re: Probleme beim Warten auf 2 Threads
 
Also, ich habe das ganze mal gestern Abend nach dem Beispiel von Panthrax in mein Programm versucht umzusetzen. Es hat leider nicht funktioniert. Ich muss doch meine ganze Struktur im Programm ändern.


Zitat:

Zitat von C.Schoch
z.B. so:
Delphi-Quellcode:
 
Var
  dwWaitResult : DWORD;
  ThreadArray : array[0..1] of THandle; // Handle Array um auf Threads zu warten
begin
  SearchForCopy := TSearchCopy.Create(false);
  SearchForCopy.FreeOnTerminate := False;
  SearchForDelete := TSearchDelete.Create(false);
  SearchForDelete.FreeOnTerminate := False;
  ThreadArray[0] := SearchForCopy.Handle; // Handle Array füllen
  ThreadArray[1] := SearchForDelete;
  repeat
    // Auf Threads warten
    dwWaitResult := msgWaitforMultipleObjects(length(ThreadArray), ThreadArray, true, 100, QS_ALLINPUT);
    if dwWaitResult <> WAIT_OBJECT_0 then
    begin
       Application.ProcessMessages; // GUI aktualisieren
    end;  
  until dwWaitResult = WAIT_OBJECT_0; // keinen Threads mehr zum warten
end;

Kann man diesen Sourcecode noch irgendwie performanter machen ? Er funktioniert, ist aber grotten langsam.

C.Schoch 27. Nov 2006 21:11

Re: Probleme beim Warten auf 2 Threads
 
Hi,
Warum langsam? bei mir funktionirt der recht schnell und gut. Die Threads sind ja von dieser Funktion unabhängig.
Solltest du allerdings aus dem Thread direkt auf ein Element der GUI zugreifen (Auch über Syncronize) dann dauert es natürlich bis zu 100 ms bis dein Code fortgesetzt wird.
Ich vermute du verwendest diesen Code in CopyandSync und aktualisierst bei jedem Durchlauf von FindFirst / FindNext den Text deiner Statusbar so wird höchstens alle 100ms eine Datei gefunden!
Das Stichwort heist hier Messages oder du setzt eine Variable im Thread/Hauptprogramm den du vor Application.ProcessMessages ausließt und in Statusbar schreibst.

RWarnecke 10. Dez 2006 12:35

Re: Probleme beim Warten auf 2 Threads
 
Zitat:

Zitat von C.Schoch
Hi,
Warum langsam? bei mir funktionirt der recht schnell und gut. Die Threads sind ja von dieser Funktion unabhängig.
Solltest du allerdings aus dem Thread direkt auf ein Element der GUI zugreifen (Auch über Syncronize) dann dauert es natürlich bis zu 100 ms bis dein Code fortgesetzt wird.

Ich habe es jetzt mal probiert, wenn ich den Wert von 100ms runtersetze auf 5. Dann geht es schneller.
Zitat:

Zitat von C.Schoch
Ich vermute du verwendest diesen Code in CopyandSync und aktualisierst bei jedem Durchlauf von FindFirst / FindNext den Text deiner Statusbar so wird höchstens alle 100ms eine Datei gefunden!

Damit liegst Du richtig. Ich setze diesen Code in meinem Projekt CopyAndSync ein. Das auslesen der Dateien erfolgt aber nicht über FindFirst / FindNext, sondern über eine Unit aus dem Synchronisationstool Pegasus von Luckie.
Zitat:

Zitat von C.Schoch
Das Stichwort heist hier Messages oder du setzt eine Variable im Thread/Hauptprogramm den du vor Application.ProcessMessages ausließt und in Statusbar schreibst.

Das mit der Variable oder Message verstehe ich da noch nicht ganz.

C.Schoch 11. Dez 2006 00:28

Re: Probleme beim Warten auf 2 Threads
 
Zitat:

Zitat von RWarnecke
Ich habe es jetzt mal probiert, wenn ich den Wert von 100ms runtersetze auf 5. Dann geht es schneller.

Was meine Vermutung bekräftigt, dass du aus den Threads heraus auf die GUI zugreifst.

Zitat:

Zitat von RWarnecke
Damit liegst Du richtig. Ich setze diesen Code in meinem Projekt CopyAndSync ein. Das auslesen der Dateien erfolgt aber nicht über FindFirst / FindNext, sondern über eine Unit aus dem Synchronisationstool Pegasus von Luckie.

Auch der benutzt Find First/Next

Zitat:

Zitat von RWarnecke
Das mit der Variable oder Message verstehe ich da noch nicht ganz.

Ich übergebe dem Thread einen Pointer auf eine in meiner Hauptanwendung definierten String den ich dann einfach im Thread mit Meldungen fülle:
Delphi-Quellcode:
  // Im Hauptprog
  SearchForCopy := TSearchCopy.Create(false);
  SearchForCopy.FreeOnTerminate := False;
  SearchForCopy.Message := @MessageString;
Delphi-Quellcode:
  //im Thread
  Message^ := 'Kopiere Datei xyz';
Und setze dann diese Meldung alle 100ms in meine Anzeige:
if dwWaitResult <> WAIT_OBJECT_0 then
begin
Label1.Caption := MessageString;
Application.ProcessMessages; // GUI aktualisieren
end;
Frei getippt!

p.s.: Es scheint ein Problem mit meinem Code (vor allem mit dem fWaitAll) zu geben schau mal hier:msgWait... sollte aber nicht weiter schlimm sein.
Damit gehts.
Delphi-Quellcode:
dwWaitResult := WaitforMultipleObjects(length(ThreadArray), @ThreadArray, true, 100);


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