Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   TThread Suspend und Resume (https://www.delphipraxis.net/190230-tthread-suspend-und-resume.html)

Codehunter 14. Sep 2016 14:48

TThread Suspend und Resume
 
Hallo zusammen!

Ich ärgere mich mal wieder mit der Threadprogrammierung herum, ist für mich immer noch steiniges Neuland ^^

Ich habe ein TThread-Objekt, das in der Execute-Prozedur eigentlich alles macht was ich will. Allerdings nur beim ersten Resume. Ein zweites Resume führt nicht wieder zur Ausführung des Execute-Codes. Das TThread-Objekt wurde Suspended erzeugt und FreeOnTerminate ist FALSE.

Ich möchte den Thread so verwenden, dass er praktisch die meiste Zeit schlafend im Hintergrund liegt. Ab und zu werden durch Nutzeraktion bestimmte Aufgaben erstellt, die der Thread im Hintergrund abarbeitet. Eine Interaktion mit dem Mainthread findet nicht statt.
Delphi-Quellcode:
procedure TmyThreadObject.Execute;
var
  E: TmyAction;
begin
  try
    while (not Terminated) and (Actions.Count > 0) do begin
      E:= Actions[0];
      // Do some Code
    end;
  finally
    Suspend;
  end;
end;
Oder mache ich hier einen Denkfehler und TThreads lassen sich gar nicht auf diese Weise "recyclen" so dass ich das Threadobjekt prinzipiell freen und bei Vorliegen neuer Actions (nicht im Sinne von TAction verstehen) ein neuer Thread erzeugt wird?

Grüße
Cody

himitsu 14. Sep 2016 14:55

AW: TThread Suspend und Resume
 
Die Execute-Methode wird nur einmal ausgeführt.
Ist die durch, dann ist der Thread fertig und kann nicht wieder neu gestartet werden.

Fazit: Eine Schleife einbauen :stupid:
Grund: Das zweite Resume setzt den Thread nach dem "Suspend" fort und der Thread beendet sich dann, da er danach nichts mehr zu tun hat.


Aber ich würde eher vom Pausieren abraten und stattdessen irgendein "WaitFor" da hin tun und dann mit Events abeiten. (Thread wartet, bis Event gefeuert wird)

dGeek 14. Sep 2016 14:55

AW: TThread Suspend und Resume
 
Ich mache es immer so:

Delphi-Quellcode:
while not Terminated do
 begin
  if bThreadSollSchlafen then
   Sleep(1000);

  if bThreadBeenden then
   begin
    // Beende den Thread
   end else
    begin
     // Führe Code aus, der ausgeführt werden muss.
    end;
 end;
Soweit ich weis, sind Resume und Suspended doch veraltet.

stahli 14. Sep 2016 15:28

AW: TThread Suspend und Resume
 
Ich schließe mich himi an.
Falls Du damit nicht zurecht kommst, kann ich mal etwas zusammenstellen.
Habe das kürzlich auch erstmalig und erfolgreich eingesetzt. :-)

dGeek 14. Sep 2016 15:39

AW: TThread Suspend und Resume
 
Das mit dem WaitFor würde mich jetzt aber auch interessieren.
Denn dann wird nicht mehr soviel Energie verbraten als mit den Sleeps.

Blup 14. Sep 2016 15:47

AW: TThread Suspend und Resume
 
Ich hoffe es wird nicht von außerhalb des Threads auf "Actions" zugegriffen.
Ohne entsprechende Absicherung wäre das ein guter Grund für unerklärliche Fehler und Programmabstürze.

Mavarik 14. Sep 2016 16:00

AW: TThread Suspend und Resume
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich hoffe Ihr seit beide auf den Delphitage... Da gibt es einen Vortrag zu diesem Thema...

auf die Schnelle

Delphi-Quellcode:
  while not(Terminated) do
    begin
      try
        FRunning := false; // Atom

        E_Event.WaitFor(FTimeOut);

        if FCancelRequest or Terminated then
          exit;

        FRunning := true;
        // Code der ausgeführt werden soll
      except
      end;
    end;
Mavarik

PS.: Und eine alte Unit für Delphi-User ohne System.Threading oder für Threads die im nano-Sekundenbereiche starten müssen...

Einfache eine eigene Klasse davon ableiten und die abstrakten Proceduren überschreiben...

himitsu 14. Sep 2016 19:01

AW: TThread Suspend und Resume
 
Zitat:

Zitat von dGeek (Beitrag 1347617)
Soweit ich weis, sind Resume und Suspended doch veraltet.

Nunja, das Einzige, was man niemals machen darf, ist ein Suspend von außerhalb des Threads.
Kein Anderer weiß wo der Thread gerade ist und kann somit den Thread auch nicht "sicher" anhalten.
(sich selber anhalten könnte ein Thread sich schon dürfen, aber da gibt es andere/bessere Wege)

Sicher = nicht an ein "ungünstigen" Stelle

Suspended starten erstellen und dann später starten ist schon noch OK, auch wenn sich die Befehle/Namen von Suspend/Resume vielleicht geändert haben mögen. :angle:

Zitat:

Zitat von dGeek (Beitrag 1347620)
Das mit dem WaitFor würde mich jetzt aber auch interessieren.
Denn dann wird nicht mehr soviel Energie verbraten als mit den Sleeps.

Sleep macht intern auch "nur" ein WaitFor und wartet auf das TimeOut :stupid:

Aber es geht sofort weiter (hier muß man immer bis zu 1000 Millisekunden warten, bis es weiter geht)
und der Thread wacht nicht ständig sinnlos zwischendurch auf ... erst, wenn das Event oder der TimeOut ausgelöst haben.

PS: FCancelRequest oder bThreadBeenden sind doch bissl doppelt, vor allem dann, wenn man daran denkt was Terminated schon macht. :zwinker:

dGeek 14. Sep 2016 19:08

AW: TThread Suspend und Resume
 
Zitat:

Aber es geht sofort weiter (hier muß man immer bis zu 1000 Millisekunden warten, bis es weiter geht)
Ich warte immer 25ms. Ist schnell genug finde ich.

Medium 14. Sep 2016 21:12

AW: TThread Suspend und Resume
 
Da auch bei einem Sleep(1) ein "wartender" Thread zu 0% CPU Auslastung führt, habe ich das bisher immer genommen. Bei mir sehen die Execute-Methoden meist so aus:
Delphi-Quellcode:
procedure TMyThread.Execute;
var
  ...
begin
  while not Terminated do
  begin
    try
      try
        if FWaiting then
          Sleep(1)
        else
        begin
          // Eigentlicher Thread-Code
          // (setzt ggf. FWaiting selbst auf true wenn die Aufgabe durch ist,
          // da ein Bool aber atomar ist, kann dies auch von aussen passieren)
        end;
      except
        on ...
      end;
    finally
      ...
    end;
  end;
end;

BUG 14. Sep 2016 21:34

AW: TThread Suspend und Resume
 
Das Primitiv was du suchst, ist ein Mutex (zB. CriticalSection unter Windows) und eine Condition-Variable. Der Mutex schützt den Zähler (und eventuell andere Datenstrukturen), auf der Condition-Variable kannst du schlafen. Das ist die Standard-Lösung für solche Probleme. So etwas mit Sleep oder Ähnlichem zu implementieren ist Frickelei (Sorry Medium :wink:).
Darauf aufbauend kannst du Warteschlangen implementieren. Dabei schläft der Thread so lange, bis er das nächste Element aus der Warteschlange nehmen kann. Auch so etwas sollte es in Threading-Bibliotheken fertig geben ... da kenne ich mich unter Delphi aber nicht aus.
Nur wenn die Synchronisation ein Flaschenhals ist, muss man nach anderen Lösungen suchen, z.B. nicht-blockierende Algorithmen.

Medium 14. Sep 2016 22:55

AW: TThread Suspend und Resume
 
Da in dem Artikel darauf hingewiesen wird, dass Windows XP Condition-Variables nicht unterstützt, wir dieses aber noch unterstützen müssen, wäre ich daran interessiert wie du das dann machen würdest. Critical Sections (in Form von TCriticalSection) nutze ich bisher, um damit geteilte Daten zu synchronisieren.
Meistens Listen, bei denen ein Teilnehmer nur hinzufügt, der andere nur entfernt, und eben diese Operationen in einer Critical Section gegeneinander verriegeln. Das
Delphi-Quellcode:
if FWaiting then Sleep(1)
aus meinem Beispiel wird in diesen Fällen oftmals auch ein
Delphi-Quellcode:
if FList.Count = 0 then Sleep(1)
.

BUG 15. Sep 2016 00:15

AW: TThread Suspend und Resume
 
Zitat:

Zitat von Medium (Beitrag 1347692)
Da in dem Artikel darauf hingewiesen wird, dass Windows XP Condition-Variables nicht unterstützt, wir dieses aber noch unterstützen müssen, wäre ich daran interessiert wie du das dann machen würdest.

Events sehen in der Richtung gut aus. Da die aber beim Warten keine CS freigeben können und nicht zählen, muss man aufpassen mit Deadlocks und vergessenen Wake-ups. Semaphoren können dir das Zählen abnehmen, machen aber auch nicht alles einfacher ... und für jedes Zählen wechselt man in das Betriebssystem.

Im Prinzip könnte das so aussehen (Pseudocode, ungetestet):
Delphi-Quellcode:
var
  cs : CriticalSection;
  ev : Event; // als auto-reset konfiguriert
  count : Integer;

procedure producer()
begin
  while (true) do
  begin
    //> Hier etwas sinnvolles machen!

    EnterCriticalSection(cs);

    // Einen Eintrag in die Queue einhängen:
    count := count + 1

    // Hier ist die Bedingung immer erfüllst,
    // aber das muss nicht bei allen denkbaren Synchronisationsmustern so sein.
    if (count > 0) then setEvent(ev); // Aufwecken!
    LeaveCriticalSection(cs);
  end;
end;

procedure consumer()
begin
  while (true) do
  begin
    EnterCriticalSection(cs);
    while (not (count > 0)) do
    begin
      // Wir verlassen die CS, damit der Producer was einhängen kann.
      LeaveCriticalSection(cs);
      WaitForSingleObject(ev); // Schlafen!
      EnterCriticalSection(cs);
    end;

    // Jetzt ist count > 0 und wir haben die CS!
    // Also können wir hier z.B. etwas aus der Queue aushängen:
    count := count - 1;

    // Bevor wir die CS verlassen, gucken wir noch mal, ob die Bedingung weiter erfüllt ist
    // und wecken gegebenenfalls noch den nächsten auf.
    if (count > 0) then setEvent(ev);
    LeaveCriticalSection(cs);
   
    //> Hier etwas sinnvolles machen!
  end;
end;
Das Event dient quasi als Condition-Variable, die CS schützt die Queue bzw. hier nur den Zähler. Man kann und sollte das Ganze in eine Queue-Implementierung kapseln, wenn man damit sinnvoll arbeiten möchte.

Disclaimer: Ich habe mich nicht eingehend mit den konkreten Windows Synchronisationsfunktionen befasst. Synchronisation ist schwer und fehleranfällig. Bitte auf Fehler/Bedenken hinweisen.

Medium 15. Sep 2016 08:01

AW: TThread Suspend und Resume
 
Interessant! SetEvent() und Co werde ich mir mal genauer anschauen müssen. Sieht erst mal nett aus. Danke dir!

Codehunter 15. Sep 2016 09:52

AW: TThread Suspend und Resume
 
Zitat:

Zitat von himitsu (Beitrag 1347616)
Die Execute-Methode wird nur einmal ausgeführt.
Ist die durch, dann ist der Thread fertig und kann nicht wieder neu gestartet werden.

Fazit: Eine Schleife einbauen :stupid:
Grund: Das zweite Resume setzt den Thread nach dem "Suspend" fort und der Thread beendet sich dann, da er danach nichts mehr zu tun hat.

Aber ich würde eher vom Pausieren abraten und stattdessen irgendein "WaitFor" da hin tun und dann mit Events abeiten. (Thread wartet, bis Event gefeuert wird)

Also mit Pausieren funktionierts jetzt bei mir ganz gut, die Schleife war ja schon da:
Delphi-Quellcode:
procedure TmyThreadObject.Execute;
var
   E: TmyAction;
begin
  while not Terminated do begin
    if Actions.Count = 0 then begin
      Sleep(1000);
      Continue;
    end;
    E:= Actions[0];
    // Do some Code
  end;
end;
Was sind denn jetzt die Vor- und Nachteile von Sleep und WaitFor?

EDIT: Die Vorgehensweise innerhalb der Execute-Methode erinnert mich inzwischen stark an die Entwicklung von Systemdiensten, die man auch in eine bedingte Schleife schickt.

dGeek 15. Sep 2016 10:18

AW: TThread Suspend und Resume
 
Delphi-Quellcode:
if Actions.Count = 0 then begin
      Sleep(1000);
      Continue;
Warum kein ...

Delphi-Quellcode:
while Actions.Count = 0 then begin
      Sleep(1000);
:stupid:

stahli 15. Sep 2016 11:22

AW: TThread Suspend und Resume
 
Zitat:

Zitat von Codehunter (Beitrag 1347715)
Was sind denn jetzt die Vor- und Nachteile von Sleep und WaitFor?

Mit einem Event kannst Du von außen triggern, dass der nächste Durchlauf erfolgen soll. Bis dahin ruht der Thread.

OlafSt 15. Sep 2016 11:51

AW: TThread Suspend und Resume
 
Mit anderen Worten:


Delphi-Quellcode:
procedure TForm1.FormClose(Sender: TObject)
begin
   MyThread.Terminate;
   MyThread.WaitFor;
end;
Beinhaltet der Thread ein Sleep(1000); wartet dein Thread eine Sekunde. Gnadenlos und völlig egal, was um ihn herum für ein Zampano abgeht. Der Aufruf von Terminate in obigem Beispiel würde also eine Sekunde blockieren, weil der Thread nicht eher reagieren kann. Der Benutzer wiederum denkt: "Mal wieder abgestürzt" und läßt sich zu Chaos-Handlungen hinreißen.

Besser wäre
Delphi-Quellcode:
procedure TForm1.FormClose(Sender: TObject)
begin
   MyThread.Terminate;
   MyThread.SetEvent(MyThreadEvent);
   MyThread.WaitFor;
end;
Der Thread wartet nun auf den Event mit einem Timeout von einer Sekunde. Durch diesen Winkelzug hast du dein Sekunden-Sleep, bekommst den Thread aber sofort terminiert.

himitsu 15. Sep 2016 13:03

AW: TThread Suspend und Resume
 
Zitat:

Zitat von Codehunter (Beitrag 1347715)
Delphi-Quellcode:
procedure TmyThreadObject.Execute;
var
   E: TmyAction;
begin
  while not Terminated do begin
    if Actions.Count = 0 then begin
      Sleep(1000);
      Continue;
    end;
    E:= Actions[0];
    // Do some Code
  end;
end;

Ich hoffe Actions ist eine TThreadList?


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