![]() |
Thread soll eine Minute warten: Sleep oder Timer?
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:
(damit ich beim Testen nicht ewig warten muss, legt sich der Thread hier nur 10 Sekunden Schlafen)
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. Im Hauptprogramm gibt es nun einen Button, mit dem man den Thread beenden kann:
Delphi-Quellcode:
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...
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; Am einfachsten wäre es wahrscheinlich, wenn man nicht 10 Sekunden wartet, sondern 10 mal eine Sekunde, so dass aus
Delphi-Quellcode:
ein
sleep(10000);
Delphi-Quellcode:
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.
i:=0
while (not terminated) and (i<10) do begin sleep(1000); inc(i); end; 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:
Und im Hauptthread gibt es dann ein Timer-Ereignis und eine Änderung beim Klicken auf den Button:
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;
Delphi-Quellcode:
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. :dancer:
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; Ich bin auf eure Rückmeldungen gespannt. Danke und bis denn Bbommel |
Re: Thread soll eine Minute warten: Sleep oder Timer?
In einem Thread ist sleep nutzbar (nur auf Terminate sollte man achten). Besser ist waitforsingleobject mit einer Zeitangabe.
Edit: Waitforsingleobject irgendwie so:
Delphi-Quellcode:
constructor Tmythread.Create(CreateSuspended: Boolean);
begin inherited Create(false); FEvent:=TEvent.Create(nil,true,false,''); end; destructor Tmythread.Destroy; begin FEvent.Free; inherited; end; procedure Tmythread.DoSomething; begin //abstract ;-) end; procedure Tmythread.Terminate; begin inherited; FEvent.SetEvent; end; procedure Tmythread.execute; begin while not terminated do begin DoSomething; FEvent.ResetEvent; FEvent.WaitFor(10000); end; end; |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Denke mal, dass das auch mit GetTickCount gehen sollte. AFAIK ist das Threadsafe.
|
Re: Thread soll eine Minute warten: Sleep oder Timer?
Hallo,
wie wärs denn mit KISS (keep it simple and stupid): Das Programm wartet nur darauf, beendet zu werden. Die Funktionalität wird im Timer Service erledigt, falls was vorliegt. Weder Thread noch sleep nötig. Gruss Reinhard |
Re: Thread soll eine Minute warten: Sleep oder Timer?
ich würde vielleicht nich direkt ein 60-Sekunden-Sleep einbauen, denn so kannst du den Thread nicht mehr so schön/schnell beenden. (vorallem wenn das Programm beendet werden soll ... wär ja blöd, wenn du dann im schlimmsten Fall noch 'ne Minute gewartet werden müßte)
CPU-schonend und unterbrechbar wäre z.B. sowas:
Delphi-Quellcode:
oder was mit WaitFor... wie sirius grad nachtrug
Var T: LongWord;
ThreadSollBeendetWerden: Boolean; ... T := GetTickCount; While (GetTockCount - T < 60000) and not ThreadSollBeendetWerden do Sleep(200); |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
Delphi-Quellcode:
durch ein
sleep(10000)
Delphi-Quellcode:
ersetzen?
waitForSingleObject(Handle,10000);
So ganz schlau werde ich daraus noch nicht - jetzt bei diesem Versuch wartet der Thread ja, bis er selbst signalisiert (also beendet, wenn ich das richtig verstanden habe) wird. Aber das bringt mich offenbar nicht so richtig weiter oder kann man dieses Signal auch manuell senden? Oder muss ich da einen extra Handler für erstellen? Ich hatte zu dem Thema auch schon im Thread-Tutorial von Luckie was gelesen, aber dort werden die wait-Funktionen ja gerade auch außerhalb des Threads eingesetzt, was ja auf mein Problem nicht so recht zutrifft. Bis denn Bommel |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Ähm, das zuerst gedachte Edit oben sollte hier als Antwort hin.
|
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
Zitat:
Nur zum Verständnis noch mal nachgefragt: Das ist ja eigentlich der gleiche Ansatz, wie ich ihn mit dem mehrfachen Hochzählen von i und dazwischen einem kürzeren sleep hatte, nur über den TickCount eleganter, weil die 60 Sekunden besser eingehalten werden können, richtig? Mein Problem mit dem Ansatz ist nur, dass es sich komisch "anfühlt", wenn andauernd ein Kontextwechsel gemacht wird, obwohl eigentlich gar nichts zu tun ist. Ich kann nur überhaupt nicht einschätzen, ob sowas auch tatsächlich nach bemerkbar ist oder ob ich mir dann den Kopf über ein eher akademisches Problem zerbreche. :) Bis denn Bommel |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
Bis denn Bommel |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
Ein simpler timer reicht doch dann völlig. |
Re: Thread soll eine Minute warten: Sleep oder Timer?
GetTickCount ist Windows API und fragt die Millisekunden seit dem letzten Systemstart ab. Da unterbricht nix.
|
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
Jetzt klarer? Bis denn Bommel |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
welche Anwendung soll denn einfrieren? Das Hauptprogramm bekommt nur Messages zum Beenden und führt diese sofort aus, oder Timer-Messages und zeigt dann Daten an oder was auch immer, was man vernünftigerweise sowieso nicht unterbricht. Ausserdem kann man das leicht gegeneinander sperren, so dass nur beendet wird, wenn gerade keine Daten verarbeitet werden, und man kann die Datenverarbeitung abbrechen, wenn das Programm beendet werden soll, beides hat nichts mit Threads zu tun. Der grösste Nachteil meiner Lösung, das muss ich zugeben, ist, dass man nicht damit angeben kann, was für tolle Multithreads man schreiben kann. Mit einfachen Lösungen macht man sich hier hochgradig unbeliebt. Also vergiss es einfach und mach einen, zwei, viele Threads. Gruss Reinhard |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
Zitat:
Ich halte beides für "gute" Möglichkeiten dieses zu regeln (WaitFor und soeine kleine Sleep-Schleife) ... es kommt nur darauf an, wieviel du implementieren willst und wie schnell das Abbrechen Programmintern sein soll ... das WaitFor... reagiert natürlich schneller, ist aber 'nen Hauch aufwendiger. |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
Das Aktualisieren der Komponenten, also das Anzeigen der Daten, geschieht dann natürlich wieder über den Hauptthread. Ich sehe nicht, wo ich da gerade zu kompliziert denke und wie man das einfacher lösen könnte. Aber wenn ich mich da irgendwie verrannt habe und du eine einfachere Lösung siehst, gerne her damit. Bis denn Bommel |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
|
Re: Thread soll eine Minute warten: Sleep oder Timer?
Ich bin für
WaitForSingleObject(hStopThreadevent, 20*1000) |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
|
Re: Thread soll eine Minute warten: Sleep oder Timer?
Zitat:
Gerade bei Datenbankanwendungen mache ich es auch, dass ich einen Thread habe, welcher die komplette Datenbankkommunikation betreibt. Wenn grad mal nix zu tun ist, dann schläft er halt. Bei mir ist allerdings alles komplett ereignisgesteuert, aber das steht ja hier nicht zum Thema. Warum sehe ich da bei Datenbanken die Notwendigkeit? Der Thread soll eine Verbindung zur Datenbank aufbauen. Und diese Verbindung soll über längere Zeiten (auch wenn grad keine Query o.ä. abgearbeitet wird) bestehen. Und wenn ich die Verbindung nicht unterbrechen will, kann ich auch nicht den Thread wegschmeißen. Dazu muss ich ihn halt schlafen legen. Warum will ich die Verbindung aufrecht erhalten. Z.B. wegen bestehenden Transaktionen (mit Rollback-Option). Oder bei Oracle-Packages wird je Session eine Instanz des "Package-Objektes" angelegt. Die Instanz benötige ich über längere Zeit. Dann gibt es noch Userverwaltungen, welche auf einer konstanten Verbindung zu Clients beruhen.... |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Sowas?
WaitForMultipleObjects(hAnyEvent, hThreadStopEvent, ... , YourTimeOut) |
Re: Thread soll eine Minute warten: Sleep oder Timer?
|
Re: Thread soll eine Minute warten: Sleep oder Timer?
Dein Edit habe ich nicht mehr gelesen.
|
Re: Thread soll eine Minute warten: Sleep oder Timer?
Hallo zusammen,
Danke euch allen für die Anregungen und auch die Diskussionen - das alles hat mir nicht nur für die eigentliche Umsetzung, sondern auch für das Wissen, warum ich das eigentlich tue, weitergeholfen. Einen hab ich aber noch. ;) Am Anfang hatte ich selbst noch eine Lösung vorgeschlagen, auf die niemand weiter eingegangen ist (wie gemein... ;) ). Das war der Ansatz, in dem sich der Thread am Ende mit Suspend selbst Schlafen gelegt hat und dann von außen wieder durch einen Timer aufgeweckt wurde. Wenn man das mit den anderen Beiträgen hier vergleicht, ist das ja fast so eine Art Zwischending zwischen dem Vorschlag von sirius (und anderen) und Frederika. Ist an dem Ansatz etwas wirklich falsch? Ist ein Timer ineffektiver als ein WaitFor? Oder ist er einfach nur unschön oder ungewöhnlich? Bis denn Bommel |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Hallo Sirius, hallo zusammen,
eine Nachfrage habe ich noch zu deinem Ansatz. Zitat:
1. Create: Du rufst den Vorgänger immer mit "false" auf - machst du das, weil sonst die Gefahr besteht, dass das Execute schon losrennt, bevor das Create durch ist? Das heißt, wenn ich will, dass der Parameter noch weiter beachtet wird, müsste das Create vollständig so aussehen:
Delphi-Quellcode:
Edit: Da hatte ich wohl Tomaten auf die Augens und true und false verwechselt ;) - Die richtige Frage wäre also: Warum startest du den Thread IMMER als nicht-suspended?
constructor Tmythread.Create(CreateSuspended: Boolean);
begin inherited Create(false); FEvent:=TEvent.Create(nil,true,false,''); if not CreateSuspended then Resume; end; 2. ResetEvent: Ist das nicht eher störend? Das Event startet doch durch die Parameter bei Create eh als nicht gesetzt. Durch dieses Reset könnte doch nun folgendes passieren: Nehmen wir an, DoSomething dauert ein paar Sekunden (eben jene Datenbank-Abfrage beispielsweise) und während diese DoSomething läuft, wird der Thread terminiert und damit das Event gesetzt. Nach dem DoSomething wird es aber erst mal zurückgesetzt, was bedeutet, dass noch mal 10 Sekunden lang ein WaitFor aufgerufen wird, obwohl man das eigentlich gar nicht mehr braucht. Oder? Mein (sinnloser) Demo-Thread funktioniert jedenfalls auch ohne das Reset genau wie gewünscht. Verzeiht meine vielen Nachfragen. Wie gesagt, ich bin halt noch neu im Thema Threads und möchte so gut wie möglich verstehen, was ich da tue, bevor ich das in Produktiv-Systeme einbaue. Bis denn Bommel |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Suspend und Resume sollte man nicht verwenden (wird zwar intern von TThread verwendet, aber egal). Ist laut msdn auch nur für das Debuggen von Processen und Threads gedacht.
Zitat:
Der Vorschlag mit dem Timer ist, dass du jedesmal einen neuen Thread und damit eine neue Verbindung aufbaust. [roter Kasten] lese ich mir gleich durch Edit: zum roten Kasten (letzter Beitrag) Punkt 1. Jep das war falsch. das muss inherited Create(CreateSupended) hin. Der Thread wird so oder so fühestens erst nach dem Constructor gestartet. Punkt 2. Hmm ja, müsste man nochmal genauer drüber nachdenken. bzw. wenn ich nochmal drüber nachdenke --> ja kann man streichen. Ich arbeite häufig mit mehreren Events, und da muss ich hin und wieder zurücksetzen. |
Re: Thread soll eine Minute warten: Sleep oder Timer?
Alles klar. Vielen Dank noch mal, jetzt kann es dann so langsam ans echte Projekt gehen. :dp:
|
AW: Thread soll eine Minute warten: Sleep oder Timer?
Das Thema ist schon etwas älter aber ich habe es so gelöst:
Code:
fcint ist die Zeit die der Thread warten soll.
constructor TBThread.Create;
begin inherited Create(true); FTerminateEvent := CreateEvent(nil, true, False, nil); end; destructor TBThread.Destroy; begin SetEvent(FTerminateEvent); CloseHandle(FTerminateEvent); inherited; end; procedure TBThread.Execute; begin while not Terminated do begin if WaitForSingleObject(FTerminateEvent, fcint) = WAIT_TIMEOUT then begin if not Terminated then DoSomething; end; end; end; procedure TBThread.TerminatedSet; begin inherited; SetEvent(FTerminateEvent); end; Und durch dass Event welches durch die überschriebene Methode TerminatedSet gesetzt wird wird das warten vorzeitig beendet. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 17:55 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz