Autor Beitrag
MSCH
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1448
Erhaltene Danke: 3

W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
BeitragVerfasst: 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:

ausblenden volle Höhe Delphi-Quelltext
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
    { Private-Deklarationen }
    fLastStart:Longint; // letzter OnTimer
    fInterval:Longint; // Anzahl MS
    fActive: Boolean; // aktiv?
    fControlProc: TNotifyEvent;
    fThreadPrio: TTreadPrio; // noch nicht genutzt
    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
//  RegisterComponents('Beispiele', [TThreadTimer]); // geht nicht !!!!
end;

{ TThreadTimer }

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  // mehrmaliges Suspend vermeiden, da diese verschachtelt sein können
      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:

ausblenden volle Höhe Delphi-Quelltext
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; // <<- Ableitung !!
  public
    procedure TimerProc(Sender:TOBject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
 MyTimer:= TThreadTimer.Create; // Timer erstellen
 MyTimer.Interval:=5000;  // alle 5 Sec
 MyTimer.OnTimer:= TimerProc; // was soll gemacht werden, Proc zuweisen
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Mytimer.Active:=True; // Button Enabled 
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MyTimer.Active:=False; // Button disabled
end;

// diese Funktion wird vom Thread aufgerufen !!!!!
// Läuft im Kontect des Hauptthreads
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 552


(D3/D7/D8) Prof.
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 414
Erhaltene Danke: 23



BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1448
Erhaltene Danke: 3

W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
BeitragVerfasst: 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:
ausblenden 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); // <<<---- hier!!! Damit geht die CPU belastung nach unten
    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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 552


(D3/D7/D8) Prof.
BeitragVerfasst: Mi 22.02.06 14:06 
Wenn du mit GetTickCount arbeitest, ist er eh nicht genau.

TIPP: WaitForSingleObject
Udontknow
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2596

Win7
D2006 WIN32, .NET (C#)
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 552


(D3/D7/D8) Prof.
BeitragVerfasst: Mi 22.02.06 14:18 
Synchronize verschickt keine Nachrichten, wohin auch?
Udontknow
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2596

Win7
D2006 WIN32, .NET (C#)
BeitragVerfasst: 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? :gruebel:

Cu,
Udontknow
MSCH Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1448
Erhaltene Danke: 3

W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1448
Erhaltene Danke: 3

W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
BeitragVerfasst: Mi 22.02.06 18:46 
ich habs, ist zwar nicht das grüne vom Ei:

("Gimmick" ist zu Testzwecken)

ausblenden 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             //<<-- Endlosschleife, also nicht abbrechbar, Ctrl-F2 in der IDE !! :-)
    Label2.Caption:=IntToStr(I);  // Anzeige dass die Variable hochzählt, gimmick
    Label2.Update;                // wirklich anzeigen gimmick
    Inc(I);                       // gimmick
    if I=5000000 then I:=0;       // Gimmick
    CheckSynchronize;             //<<-- das ist es, siehe unten
  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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2596

Win7
D2006 WIN32, .NET (C#)
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1448
Erhaltene Danke: 3

W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
BeitragVerfasst: 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?