Autor Beitrag
user32
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 55
Erhaltene Danke: 5



BeitragVerfasst: Fr 02.05.14 16:49 
Hallo,
ich muss eine Funktion 20 bis 50 mal in der Sekunde ausführen, das heißt also alle 50 bis 20 Millisekunden, oder eben auch mal krumme Zahlen wie 33.33ms.
Und zwar sollte das relativ genau sein!

Wie wir alle wissen, ist der TIMER dafür nicht geeignet.
Ich dachte da zuerst an QueryPerformanceCounter, allerdings fehlt mir da der Ansatz, da man den ja eigentlich meist dazu nutzt um Zeit zu messen.
Irgendwelche Ansätze oder Ideen?

WINDOWS ist zwar kein Echtzeit-Betriebssystem, aber irgendwie muss es ja gehen.
Wie machen das Programme wie FRAPS & Co?
hathor
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Fr 02.05.14 17:39 
Genauigkeit von SLEEP():

Beispiel:
Sleep(0) = 0,00089 msec
Sleep(1) = 1,86880 msec
Sleep(2) = 2,78223 msec
Sleep(3) = 3,71574 msec
Sleep(4) = 4,85675 msec
Sleep(5) = 5,64122 msec
Sleep(6) = 6,51092 msec
Sleep(7) = 7,51761 msec
Sleep(8 ) = 8,49485 msec
Sleep(9) = 9,28780 msec
Sleep(10) = 10,25834 msec
Sleep(11) = 12,02005 msec
Sleep(12) = 12,93348 msec
Sleep(13) = 13,48234 msec
Sleep(14) = 14,51625 msec
Sleep(15) = 15,62513 msec
Sleep(16) = 16,63896 msec
Sleep(17) = 17,25342 msec
Sleep(18 ) = 18,27261 msec
Sleep(19) = 19,54302 msec
Sleep(20) = 20,70098 msec

Erstellt mit:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
procedure TForm2.Button2Click(Sender: TObject);
var a, b, c: Int64; r: real; i: Integer;
begin
For i:=0 to 20 do
BEGIN
  QueryPerformanceFrequency(a);
  QueryPerformanceCounter(b);
  Sleep(i);
  QueryPerformanceCounter(c);
  r:= (c - b) * 1000 / a;
  Memo1.lines.add('Sleep('+IntToStr(i)+') = '+ FloatToStrF(r, ffFixed, 125)+' msec');
  Application.ProcessMessages;
END;
end;


SLEEP-Ersatz:
www.entwickler-ecke.....php?p=676092#676092

oder:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var
  a, b,  d: Int64;
begin
  QueryPerformanceFrequency(a);
  QueryPerformanceCounter(b);
  repeat
  QueryPerformanceCounter(d);
  until  ((d-b)*1000000/a)  > 100 ; //100 µs
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Fr 02.05.14 19:01 
Das sinnvollste dafür ist ein eigener Thread. Der kann dann ständig in einer Schleife die Zeit prüfen (mit QueryPerformanceCounter) usw. ohne damit die GUI lahmzulegen. Dadurch ist dessen Genauigkeit sehr viel genauer. Die Priorität des Threads sollte man herabsetzen, damit der Thread nicht zu viel CPU Last zieht, wenn andere Programme diese benötigen.
hathor
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Fr 02.05.14 22:42 
Wie soll das gehen?
Der Thread läuft und das übrige Programm kümmert sich NICHT darum...

Ist mein Beispiel falsch?

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:
type
  TMyThread = class(TThread)
  private
    { Private declarations }
  protected
    procedure Execute; override;
  end;


implementation

{$R *.dfm}

procedure MM(s:string); begin Form2.Memo1.Lines.Add(s); end;

procedure TMyThread.Execute;
var
  a, b,  d: Int64;
begin
  QueryPerformanceFrequency(a);
  QueryPerformanceCounter(b);
  repeat
  QueryPerformanceCounter(d);
  until  ((d-b)*1000000/a)  > 1000 ; //1000 µs = 1 ms
end;

procedure Delay;
var MyThread: TMyThread;
begin
MyThread := TMyThread.Create(true);
MyThread.Priority := tpNormal;
MyThread.FreeOnTerminate := true;
MyThread.Resume;
end;

procedure TForm2.Button4Click(Sender: TObject);
var start, ende : Cardinal;
begin
start:= GetTickCount;
   Delay;
ende:= GetTickCount;
MM(INTTOSTR(ende-start));
end;


Besser so:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
procedure MM(s:string); begin Form2.Memo1.Lines.Add(s); end;

procedure MyDelay(Milliseconds: Integer);
var
  a, b,  c: Int64;
  r: real;
begin
  QueryPerformanceFrequency(a);
  QueryPerformanceCounter(b);
  repeat
    QueryPerformanceCounter(c);
    r:= (c - b) * 1000 / a;
                Application.ProcessMessages; //WICHTIG!
   until  r  >  Milliseconds ;

  MM(FloatToStrF(r, ffFixed, 123)+' msec');
end;

procedure TForm2.Button8Click(Sender: TObject);
begin
  MyDelay(1000);
end;


Änderung für die Eingabe von Dezimalzahlen:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
procedure MyDelay(Milliseconds: real);
var
  a, b,  c: Int64;
  r: real;
begin
  QueryPerformanceFrequency(a);
  QueryPerformanceCounter(b);
  repeat
    QueryPerformanceCounter(c);
    r:= (c - b) * 1000 / a;
        if Milliseconds >1 then Application.ProcessMessages; //WICHTIG!
  until  r  >=  Milliseconds ;

  MM(FloatToStrF(r, ffFixed, 123)+' msec');
end;

procedure TForm2.Button10Click(Sender: TObject);
begin
 MyDelay(20.333);   //  Millisec
end;
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Sa 03.05.14 11:15 
user profile iconhathor hat folgendes geschrieben Zum zitierten Posting springen:
Ist mein Beispiel falsch?
Ich meinte, dass die Aktionen an sich im Thread ablaufen sollten. Denn dann kann man einfach in einer Schleife die Zeit pollen wie du es ja auch schon geschrieben hast. Nur dass man nicht das zeitlich absolut unberechenbare Application.ProcessMessages braucht. Wenn man das rein macht, kann man das ganze vergessen. Denn dann braucht nur die Abarbeitung einer Nachricht mal länger dauern und schon hat man die Zeit verpasst...

Deshalb ist das sinnvollste um wirklich möglichst genau an die Zeitvorgaben zu kommen ein Thread, der pollt was das Zeug hält ohne dabei von etwas anderem abhängig zu sein und dabei ggf. einen CPU Kern auch auslastet. Damit bei Single Core nix blockiert noch mit etwas niedrigerer Priorität.

Die Aktion an sich muss dann auch im Thread laufen, da ansonsten bei Verwendung von Synchronize oder ähnlichem wiederum unberechenbar viel Zeit drauf geht.

Auf diese Weise kann man mit einer Quad Core CPU (damit genug Kerne für andere Aufgaben bleiben) schon sehr genaue Zeiten erzielen, wenn auch nicht so gut wie ein Echtzeit-Betriebssystem. Je nach notwendiger Genauigkeit macht auch ggf. eine hohe Priorität für den Thread Sinn, wenn man weiß, dass ein Kern dafür komplett genutzt werden kann.
IhopeonlyReader
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 600
Erhaltene Danke: 23


Delphi 7 PE
BeitragVerfasst: Sa 03.05.14 18:58 
sonst kannst du (hoffe das ist genau genug) im execute eines threads auch

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
procedure TMyThread.Execute;
var
 AZ: TDatetime;
const
 WZ = 1000 / (60*60*24*1000); //1000 ms wartezeit
begin
AZ := Now;
while not Terminated do
  begin
  while (AZ+WZ > Now) do begin end//nichts machen :D (sonst ggf. application.processmessages falls du in der gui arbeitest)
  AZ := Now;
  //WAS du alle WZ machen willst
  end;
end;

_________________
Sucht "neueres" Delphi :D
Wer nicht brauch was er hat, brauch auch nicht was er nicht hat!
SMO
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 120
Erhaltene Danke: 18


D2005 Personal
BeitragVerfasst: Di 06.05.14 23:05 
user profile iconhathor hat folgendes geschrieben Zum zitierten Posting springen:
Genauigkeit von SLEEP():

Die Genauigkeit von Sleep hängt erheblich von der Timerfrequenz ab! Die kann man mit timeBeginPeriod (unit MMSystem) festlegen. Feinste Genauigkeit ist 1 Millisekunde, Standard ist (bei mir, Windows 8 ) 15,625 ms. Die Frequenz dieses Timers ist global und beeinflusst nicht nur die Auflösung von timeGetTime (ebenfalls MMSystem) sondern wie gesagt auch von Sleep.

Ich hatte gerade erst in einem Projekt, das Sleep benutzt, den Fall, dass alles wunderbar lief wenn das Programm von der Delphi IDE aus gestartet wurde, aber unerklärlicherweise alles etwas langsamer war wenn die IDE geschlossen und die Exe gestartet wurde. Grund war, dass die IDE wohl für sich an irgendeiner Stelle timeBeginPeriod(1) aufruft, wovon alle Prozesse im System "profitieren".

Das nur als Anmerkung, um vielleicht jemandem eine nervenaufreibende Fehlersuche zu ersparen.
Ergo: Wer Sleep benutzt und hohe Genauigkeit erwartet, sollte auch explizit timeBeginPeriod(1) (und timeEndPeriod) aufrufen.
Popov
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Mi 07.05.14 01:34 
Die Frage die sich hier stellt ist - soll die Funktion z. B. 50 mal in der Sekunde ausgeführt werden oder eher alle 1/50 Sekunde eine Funktion ausgeführt werden?

Ich hab mal eine Animations-Komponente geschrieben die eine Animation genau abspielen sollte. Was ich nicht wollte war, dass die Animation, falls Windows ausgelastet ist, langsamer abläuft oder gar stehen bleibt. Vielmehr sollte jedes Frame sozusagen in Realtime abgespielt werden.

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
uses
  DateUtils;

var
  NowZero: TDateTime;

function GetFrame: Integer;
const
  MSInterval = 20;      //Interval in Millisekunden zum anzeigen
  FramesPerSecond = 50//Anzahl Frames pro Sekunde
var
  MSBetween: Int64;
begin
  MSBetween := MilliSecondsBetween(NowZero, Now);
  Result := (MSBetween div MSInterval) mod FramesPerSecond;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  NowZero := Now;
end;


Das funktioniert in etwa so: ich will 50 mal in einer Sekunde ein neues Frame zeigen. Das macht ein Interval von 20 ms (= 1 sec / 50). Die Animation hat 30 Frames pro Sekunde. Somit ist das nicht ganz synchron, ist aber nicht schlimm, wird angepasst.

Zu einem bestimmten Zeitpunkt wird eine Zeit ermittelt, sie ist die Null-Zeit. An ihr wird im Verhältnis zu der aktuellen Zeit berechnet welches Frame aktuell dran ist. Die Rechnung ist einfach: Zeitunterschied div Interval und das alles mod Anzahl Frames per Sekunde.

Egal wie oft ich nun die Funktion aufrufe, es wird immer das passende Frame rausgegeben. Man kann das ganze also mit einem Timer mit der Intervaleinstellung von 1 ms aufrufen, es wird immer die richtige Framenummer angegeben.

Es ist also egal ob der Timer genau ist, ist er zu schnell, wird die gleiche Framenummer mehrmals rausgegeben, ist er zu langsam, werden einige Frames übergangen.

_________________
Popov

Für diesen Beitrag haben gedankt: Martok