Autor |
Beitrag |
MSCH
Beiträge: 1448
Erhaltene Danke: 3
W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
|
Verfasst: Mi 22.02.06 12:13
Hallo,
hier mal ne Idee, wie man einen Timer als Thread realisieren kann.
ist keine visuelle Komponente.
Hier der Quelltext der Unit:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106:
| unit ThreadTimerCl;
interface
uses SysUtils, Classes,Windows;
type TTreadPrio = (tpIdle,tpLowest,tpLower,tpNormal,tpHigher,tpHighest,tpTimeCritical); TThreadTimer = class(TThread) private fLastStart:Longint; fInterval:Longint; fActive: Boolean; fControlProc: TNotifyEvent; fThreadPrio: TTreadPrio; procedure WriteFInterval(const Value: longint); procedure WriteFActive(const Value: Boolean); procedure InternalSync; virtual; procedure WriteFControlProc(const Value: TNotifyEvent); procedure writeThreadPrio(const Value: TTreadPrio); public constructor Create; procedure Execute;override; published property Interval: longint read fInterval write WriteFInterval; property Active: Boolean read fActive write WriteFActive; property Priority: TTreadPrio read fThreadPrio write writeThreadPrio; property OnTimer: TNotifyEvent read fControlProc write WriteFControlProc; end;
procedure Register;
implementation
procedure Register; begin end;
constructor TThreadTimer.Create; begin Inherited Create(true); fControlProc:=nil; fLastStart:=0; Active:=False; end;
procedure TThreadTimer.Execute; var I:Longint; begin while not Terminated do begin I:= GetTickCount; if I >= fLastStart+fInterval then begin Synchronize(InternalSync); fLastStart:= I; end; end; end;
procedure TThreadTimer.InternalSync; begin if assigned(fControlProc) then FControlProc(Self); end;
procedure TThreadTimer.WriteFActive(const Value: Boolean); begin fActive := Value; if fActive then begin while Suspended do Resume; end else Suspend; end;
procedure TThreadTimer.WriteFControlProc(const Value: TNotifyEvent); var OldState:Boolean; begin oldState:=Active; Active:=False; fControlProc := Value; Active:= oldState; end;
procedure TThreadTimer.WriteFInterval(const Value: longint); var OldState:Boolean; begin oldState:=Active; Active:=False; fInterval := Value; Active:= oldState; end;
procedure TThreadTimer.writeThreadPrio(const Value: TTreadPrio); var OldState:Boolean; begin oldState:=Active; Active:=False; fThreadPrio := Value; Active:= oldState; end;
end. |
hier mal eine Musteranwendung,
einfach eine Form mit einem Label und 2 Buttons:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54:
| unit test;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls,ThreadTimerCl, StdCtrls;
type TForm1 = class(TForm) Label1: TLabel; Button1: TButton; Button2: TButton; procedure Button2Click(Sender: TObject); procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private myTimer: TThreadTimer; public procedure TimerProc(Sender:TOBject); end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject); begin MyTimer:= TThreadTimer.Create; MyTimer.Interval:=5000; MyTimer.OnTimer:= TimerProc; end;
procedure TForm1.Button1Click(Sender: TObject); begin Mytimer.Active:=True; end;
procedure TForm1.Button2Click(Sender: TObject); begin MyTimer.Active:=False; end;
procedure TForm1.TimerProc(Sender: TOBject); begin Label1.Caption:= FormatDateTime('dd.mm.yyyy hh:nn:ss',now); end;
end. |
viel Spass
MSch
_________________ ist das politisch, wenn ich linksdrehenden Joghurt haben möchte?
|
|
Spaceguide
Beiträge: 552
(D3/D7/D8) Prof.
|
Verfasst: Mi 22.02.06 12:24
Wenn ich mir deinen Code anschaue bekomme ich den Eindruck, dass der Timer gerne 100% CPU-Last zieht, nicht gerade wünschenswert.
|
|
Tastaro
Beiträge: 414
Erhaltene Danke: 23
|
Verfasst: Mi 22.02.06 12:47
Hohe CPU-Last zieht der nur, wenn man seine Priorität entsprechend hochschraubt. Und dann soll und muss er das auch.
Beste Grüße
Tastaro
|
|
MSCH
Beiträge: 1448
Erhaltene Danke: 3
W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
|
Verfasst: Mi 22.02.06 13:43
bei mir im Leerlauf (ich hab ne Intel-Kiste 3GHz, Hypertr.) und bei
priority:=tpIdle zieht er auf einer CPU ca. 40-50 %. Das ist aber normal.
Interessant ist es, wenn man nebenbei was anderes machen möchte. Im Gegensatz zum Timer der nachrichtenbasierend ist,
läuft der Thread auch, wenn andere Rechenintensive Funktionen/App. laufen und das ist ja der Sinn.
Er muss ja rechenzeit "verbraten", er rödelt ja eine "Endlos"-Schleife ab in der er die Zeiten
vergleicht. (while not terminated do...)
Ihr könnt aber die Execute-Methode ergänzen:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| procedure TThreadTimer.Execute; var I:Longint; begin while not Terminated do begin I:= GetTickCount; sleep(1); if I >= fLastStart+fInterval then begin Synchronize(InternalSync); fLastStart:= I; end; end; end; |
allerdings ist der Timer nicht mehr genau.
grez
MSch
_________________ ist das politisch, wenn ich linksdrehenden Joghurt haben möchte?
|
|
Spaceguide
Beiträge: 552
(D3/D7/D8) Prof.
|
Verfasst: Mi 22.02.06 14:06
Wenn du mit GetTickCount arbeitest, ist er eh nicht genau.
TIPP: WaitForSingleObject
|
|
Udontknow
Beiträge: 2596
Win7
D2006 WIN32, .NET (C#)
|
Verfasst: Mi 22.02.06 14:13
Hallo!
Du verwendest Synchronize, damit ist das ganze imho wieder nachrichtenbasierend wie der normale Timer. Das Ereignis würde niemals aufgerufen, wenn man gerade innerhalb des Hauptthreads arbeitet und nicht zwischendurch Application.ProcessMessages aufruft.
Was du da in der Testunit geschrieben hast, stimmt so nicht: Die Timerproc-Methode würde durch den vorherigen Aufruf von Synchronize eben innerhalb des VCL-Hauptthread-Kontextes aufgerufen. Wäre dem nicht so, hättest du bestimmt schon ein paar Überraschungen erlebt, da VCL-Steuerelemente niemals von anderen Threads manipuliert werden dürfen.
Übrigens lässt sich RegisterComponents nicht aufrufen, weil ein TThread-Nachfahre nun mal kein TComponent-Nachfahre ist. Du müsstest dafür den Thread in einer Komponentenklasse kapseln.
Cu,
Udontknow
|
|
Spaceguide
Beiträge: 552
(D3/D7/D8) Prof.
|
Verfasst: Mi 22.02.06 14:18
Synchronize verschickt keine Nachrichten, wohin auch?
|
|
Udontknow
Beiträge: 2596
Win7
D2006 WIN32, .NET (C#)
|
Verfasst: Mi 22.02.06 14:30
Vielleicht nicht direkt in Sinne von Messages, aber irgendwie muss doch der Hauptthread informiert werden, daß eine Methode in seinem Kontext auszuführen ist.
Der Thread kann ja nun nicht einfach zu jeder Zeit angehalten und mit einer anderen Aufgabe zwischendurch betraut werden, er muss selber entscheiden, wann er diese Sachen abarbeitet. Daher wird niemals so ein Event "zwischendurch"
ausgelöst. Oder habe ich da irgendwo einen Denkfehler?
Cu,
Udontknow
|
|
MSCH
Beiträge: 1448
Erhaltene Danke: 3
W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
|
Verfasst: Mi 22.02.06 18:25
ich grüble grad über den Satz in der OH:
Zitat: | Synchronize wartet, bis der Haupt-Thread in die Botschaftsschleife eintritt, und führt dann die übergebene Methode aus. |
hat wer ne Idee?
grez
msch
_________________ ist das politisch, wenn ich linksdrehenden Joghurt haben möchte?
|
|
MSCH
Beiträge: 1448
Erhaltene Danke: 3
W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
|
Verfasst: Mi 22.02.06 18:46
ich habs, ist zwar nicht das grüne vom Ei:
("Gimmick" ist zu Testzwecken)
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| procedure TForm1.Button1Click(Sender: TObject); var I:Longint; begin I:=0; Mytimer.Active:=True; sleep(1); while True do begin Label2.Caption:=IntToStr(I); Label2.Update; Inc(I); if I=5000000 then I:=0; CheckSynchronize; end; end; |
Also, nach dem lesen verschiedener Bücher;
VCL-Kompos sind nicht Thread-Sicher, daher muss ein Zugriff immer via Synchronize erfolgen, was
wie oben bereits erläutert, einen Zugriff auf die Nachrichtenschleife notwendig macht. Aber;
lt. OH reicht ein CheckSynchronize im Hautpthread aus, um den Haupttread als auch den/die Teiltrheads laufen zu lassen
ohne mit Application.ProzessMessages gleich mit Kanonen zu schiessen, (was aber wohl aufs selbige herausläuft)
grez
msch
_________________ ist das politisch, wenn ich linksdrehenden Joghurt haben möchte?
|
|
Udontknow
Beiträge: 2596
Win7
D2006 WIN32, .NET (C#)
|
Verfasst: Mi 22.02.06 19:11
Ahja, jetzt wird es viel klarer!
Alle GUI-Anwendungen initialisieren ja das Objekt "Application" (Unit Forms), in dem die Botschaftsbehandlung läuft. Innerhalb der Botschaftsbehandlung (TApplication.WndProc bzw. TApplicaton.Idle) findet ebenfalls ein Aufruf von CheckSynchronize statt, sodaß also alle
Aufrufe von Synchronize dann bearbeitet werden, wenn man ...
a) in die Botschaftsbehandlungsschleife zurückkehrt,
b) manuell CheckSynchronize in Abständen aufruft, was einem aber je nach Aktion nicht immer weiterbringt, oder
c) mit "Kanonen auf Spatzen schiesst",also Application.ProcessMessages aufruft.
Im Endeffekt also tatsächlich eine "Benachrichtigungs"-Mechanik, die nicht subito reagiert.
Cu,
Udontknow
|
|
MSCH
Beiträge: 1448
Erhaltene Danke: 3
W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
|
Verfasst: Mi 22.02.06 19:17
ja-leider, es sei denn du nimmst
Zitat: |
Grafikobjekte sind thread-sicher. Sie benötigen den VCL-Haupt-Thread, um auf die folgenden Objekte zuzugreifen: TFont , TPen , TBrush , TBitmap , TMetafile (nur VCL) und TIcon . Canvas-Objekte (Zeichenflächen) können außerhalb der Methode Synchronize verwendet werden, indem man die Objekte sperrt . |
aus OH.
Dann könntest du diese Objekte innerhalb der Execute-Methode bearbeiten, z.b. die Uhrzeit painten.
was allerdings nen mächtigen Overhead bringt um Objekte zu sperren.
grez
msch
_________________ ist das politisch, wenn ich linksdrehenden Joghurt haben möchte?
|
|
|