Einzelnen Beitrag anzeigen

Bbommel

Registriert seit: 27. Jun 2007
Ort: Köln
652 Beiträge
 
Delphi 12 Athens
 
#1

Thread soll eine Minute warten: Sleep oder Timer?

  Alt 2. Feb 2009, 11:38
Hallo zusammen,

nachdem ich mich jahrelang um Threads herumdrücken konnte (außer vor ein paar Jahren in der Uni in der Theorie), komme ich nun an dem Thema wohl nicht mehr vorbei, da ich in einem Projekt eine periodische Datenbank-Abfrage implementieren möchte, die den Benutzer aber nicht weiter stören soll. Konkret soll also einmal pro Minute eine MSSQL-Datenbank abgefragt werden und nur, falls dort bestimmte Informationen vorliegen, soll der Benutzer bei seiner eigentlichen Arbeit gestört werden.

Ich habe nun noch ein Konstruktions- bzw. Verständnisproblem. Wie schaffe ich es am elegantesten, dass der Thread einerseits eine Minute lang nix macht, nachdem die Datenbank abgefragt wurde, andererseits aber dennoch ziemlich schnell terminiert wird, sobald der Hauptthread das wünscht (weil das Programm beendet wird oder aus anderen Gründen). Denn: Wenn man den Thread mit "sleep" Schlafen schickt, bekommt er von einem Terminate ja erst mal nix mehr mit.

Ich hab hier in der DP schon ein bisschen geschmökert, was mir auch die eine oder andere Idee gebracht hat, aber so richtig sicher bin ich mir noch nicht, was nun die "ultimative" Lösung ist (obwohl ich jetzt eher erwartet hätte, dass das ein Standard-Problem wäre - nicht richtig gesucht?) Wobei ich jetzt beim Tippen das Gefühl bekommen habe, auf einem guten Weg zu sein...

Ich hab mir mal ein kleines Beispiel gebastelt, das mit Datenbanken noch gar nix macht (ist ja auch nicht mein eigentliches Problem... hoffentlich) und mit dem sich die Überlegungen ganz gut zeigen lassen:

Die Thread-Klasse:

Delphi-Quellcode:
unit thread;

interface

uses
  Classes, SyncObjs;

type
  TCalcThread = class(TThread)
  private
    { Private-Deklarationen }
    resultText: string;
  protected
    procedure Execute; override;
    procedure ShowResult;
  public
    calcValue: integer;
    constructor Create(CreateSuspended: Boolean);
  end;

var CriticalParams : TCriticalSection;

implementation

uses main, sysUtils;

{ Wichtig: Methoden und Eigenschaften von Objekten in visuellen Komponenten dürfen
  nur in einer Methode namens Synchronize aufgerufen werden, z.B.

      Synchronize(UpdateCaption);

  und UpdateCaption könnte folgendermaßen aussehen:

    procedure TCalcThread.UpdateCaption;
    begin
      Form1.Caption := 'Aktualisiert in einem Thread';
    end; }


{ TCalcThread }

procedure TCalcThread.ShowResult;
begin
  thread1_main.LabelResult.Caption:=resultText;
end;

procedure TCalcThread.Execute;
var myValue: integer;
begin
  { Thread-Code hier einfügen }
  while not terminated do begin
    CriticalParams.Acquire;
    myValue:=calcValue;
    CriticalParams.Release;

    if myValue=-1 then
      resultText:='nix gesetzt.'
    else
      resultText:=intToStr(myValue*80);
    Synchronize(ShowResult);
    sleep(10000);
  end;
end;

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

  calcValue:=-1;
end;

end.
(damit ich beim Testen nicht ewig warten muss, legt sich der Thread hier nur 10 Sekunden Schlafen)

Im Hauptprogramm gibt es nun einen Button, mit dem man den Thread beenden kann:

Delphi-Quellcode:
procedure Tthread1_main.ButtonStopThreadClick(Sender: TObject);
begin
  theThread.Terminate;

  // warten bis der Thread beendet wurde...
  theThread.WaitFor;
  messageDlg('Der Thread ist durch.',mtInformation,[mbOk],0);
  theThread.Free;
  theThread:=nil;
end;
Das Problem sollte klar sein: Es kann bis zu 10 Sekunden dauern, bis der Thread nach dem Klick auf den Button wirklich beendet wird. Am einfachsten wäre es ja, wenn man das sleep irgendwie von außen unterbrechen könnte - also das Hauptprogramm schickt einen Weckruf, der den Wecker einfach etwas vorstellt, aber sowas habe ich nicht gefunden. Also wird man wohl drumrum basteln müssen...

Am einfachsten wäre es wahrscheinlich, wenn man nicht 10 Sekunden wartet, sondern 10 mal eine Sekunde, so dass aus
    sleep(10000); ein
Delphi-Quellcode:
i:=0
while (not terminated) and (i<10) do begin
  sleep(1000);
  inc(i);
end;
würde. Der Hauptthread bekäme dann wohl viel schneller eine Rückmeldung, aber irgendwie fühlen sich diese ständigen Wechsel in den Thread nicht so richtig gut an. Hat irgendwie was vom aktiven Warten.

Als andere Alternative wollte ich jetzt eigentlich vorschlagen, dass ein Timer den Thread periodisch freigibt und neu erzeugt, aber beim Schreiben kam mir jetzt gerade der Gedanke, dass ein Timer wahrscheinlich ein guter Ansatz ist, aber ein Suspend/Resume wahrscheinlich besser, als das Dingen zu zerstören und neu anzulegen.

Das heißt, die Execute-Methode vom Thread sähe dann so aus:

Delphi-Quellcode:
procedure TCalcThread.Execute;
var myValue: integer;
begin
  { Thread-Code hier einfügen }
  while not terminated do begin
    CriticalParams.Acquire;
    myValue:=calcValue;
    CriticalParams.Release;

    if myValue=-1 then
      resultText:='nix gesetzt.'
    else
      resultText:=intToStr(myValue*80);
    Synchronize(ShowResult);
    Suspend;
  end;
end;
Und im Hauptthread gibt es dann ein Timer-Ereignis und eine Änderung beim Klicken auf den Button:

Delphi-Quellcode:
procedure Tthread1_main.TimerThreadTimer(Sender: TObject);
begin
  theThread.Resume;
end;

procedure Tthread1_main.ButtonStopThreadClick(Sender: TObject);
begin
  TimerThread.Enabled:=false;
  theThread.Terminate;
  theThread.Resume; // damit das Dingen auch merkt, dass er beendet wurde...

  // warten bis der Thread beendet wurde...
  theThread.WaitFor;
  messageDlg('Der Thread ist durch.',mtInformation,[mbOk],0);
  theThread.Free;
  theThread:=nil;
end;
Ist da irgendein Denkfehler drin oder ist das die "ultimative Lösung", nach der ich gesucht habe? Mir kommt sie gerade eigentlich ganz gut vor.

Ich bin auf eure Rückmeldungen gespannt.

Danke und bis denn
Bbommel
  Mit Zitat antworten Zitat