Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Frage zu Timer


Daniel L. - Sa 15.12.12 12:12
Titel: Frage zu Timer
Hallo

Angenommen, ein Timerintervall ist kleiner als die Zeit, die der durch das OnTimer-Ereigniss auszuführende Code braucht -- landen dann die Ereigniss in einer Warteschleife, um sie nach und nach abzuarbeiten, was ja dann irgenwann zu einem Überlauf führen müsste --
oder wird ein Timerereigniss nur dann "ausgesendet", wenn die Anwengung "nichst zu tun hat"?

In meiner Anwednugn werden langwierige Zeichnungen durch Timerimpulse ausgeführt (Canvas),
und ich will sicher stellen, dass es nicht zu einem wie oben beschriebenen Problem führen kann.
Die Anwendug soll ja auch auf langsameren Rechnern laufen können.

Danke schon mal und Gruss: Daniel


Moderiert von user profile iconNarses: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am Sa 15.12.2012 um 19:42


Mathematiker - Sa 15.12.12 12:40

Hallo,
die nächste Ausführung der Timer-Methode erfolgt erst, wenn die vorhergehende beendet wurde.
Ich kenne diesen Effekt auf meinem alten Computer sehr gut. Gleichgültig, wie klein man das Timer-Interval wählt, ausgeführt wird die Routine erst, wenn die Berechnung/Darstellung wieder möglich ist.

Beste Grüße
Mathematiker


WasWeißDennIch - Sa 15.12.12 13:22

Da der TTimer die WM_TIMER-Message verwendet, landen diese in der Messagequeue. Ob die nun irgendwann "überläuft" kann ich magels Erfahrung gar nicht sagen, aber man könnte ja Vorsorge treffen, indem man den Timer im OnTimer-Event anhält, abarbeitet und wieder startet. Die Genauigkeit leidet natürlich etwas darunter, aber erstens hat die WM_TIMER-Message nur untergeordnete Priorität und zweitens ist diese Genauigkeit eh nicht gegeben, wenn die Abarbeitung zu lange dauert.


Narses - Sa 15.12.12 20:42

Moin!

user profile iconWasWeißDennIch hat folgendes geschrieben Zum zitierten Posting springen:
Da der TTimer die WM_TIMER-Message verwendet, landen diese in der Messagequeue.
AFAIR wird der WinAPI-Timer immer wieder neu nach einem Ereignis aufgesetzt, so dass keine Ereignisse in irgendeiner Queue landen. :idea:

cu
Narses


WasWeißDennIch - Sa 15.12.12 21:20

Ich habe gerade im MSDN WM_TIMER, SetTimer und TimerProc durchforstet, konnte aber nichts dergleichen entdecken. Woher hast Du diese Info?


Tilman - Sa 15.12.12 21:29

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Hallo,
die nächste Ausführung der Timer-Methode erfolgt erst, wenn die vorhergehende beendet wurde.


Leider nein, wie man leicht mit einer ShowMessage feststellen kann:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TForm1.Timer1Timer(Sender: TObject);
  var zufall: Integer;
begin
  zufall := random(100);
  writeln('start '+IntToStr(zufall));
  showmessage('Unterbrechung');
  writeln ('fertig '+IntToStr(zufall));
end;


//Edit: Bild hinzugefügt
unterbrechung


Mathematiker - Sa 15.12.12 22:35

Hallo Tilman,
user profile iconTilman hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Hallo,
die nächste Ausführung der Timer-Methode erfolgt erst, wenn die vorhergehende beendet wurde.

Leider nein, wie man leicht mit einer ShowMessage feststellen kann:

Tut mir leid, Dein Gegenbeispiel funktioniert nur durch das Einfügen von Showmessage. Ich habe es mit

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
procedure TForm1.Timer1Timer(Sender: TObject);
var x,y:real; i,zufall: Integer;
begin
   zufall := random(100);
   listbox1.items.add('start '+IntToStr(zufall));
   x:=pi/2;
   y:=0;
   for i:=1 to 10000000 do begin
     y:=sin(x)+cos(x);
     x:=y;
   end;  
   listbox1.items.add('fertig '+IntToStr(zufall));
   if listbox1.items.count>10 then begin
     timer1.enabled:=false;
     listbox1.items.add(inttostr(gettickcount-zeit));
     end;
end;

ausprobiert und trotz timer.iterval=1 und wesentlich längerer Berechnungszeit als 1 ms, werden die start- und fertig-Werte korrekt hintereinander ausgegeben. Für die 6 Timer-Aufrufe brauchte mein Computer 19 Sekunden!

Ich vermute, dass nach einem Aufruf von showmessage eine Art application.processmessages gerufen wird; sonst würden ja alle anderen Prozesse "stehen" bis man OK gedrückt hat. Dadurch wird auch ein neues Starten der Timer-Methode möglich.
Enthält die Timer-Methode keinen derartigen Code, dürfte meine Aussage korrekt sein.

Beste Grüße
Mathematiker


WasWeißDennIch - Sa 15.12.12 22:55

Wieso sollten andere Prozesse stehen, bis man OK klickt? Ich kann Dir da nicht folgen.


Narses - So 16.12.12 00:16

Moin!

user profile iconWasWeißDennIch hat folgendes geschrieben Zum zitierten Posting springen:
Ich habe gerade im MSDN WM_TIMER, SetTimer und TimerProc durchforstet, konnte aber nichts dergleichen entdecken. Woher hast Du diese Info?
Aus dem VCL-Source. ;) Allerdings hab ich mich wohl doch eher an Quatsch erinnert :? denn es ist tatsächlich so:

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:
procedure TTimer.WndProc(var Msg: TMessage);
begin
  with Msg do
    if Msg = WM_TIMER then
      try
        Timer;
      except
        Application.HandleException(Self);
      end
    else
      Result := DefWindowProc(FWindowHandle, Msg, wParam, lParam);
end;

procedure TTimer.UpdateTimer;
begin
  KillTimer(FWindowHandle, 1);
  if (FInterval <> 0and FEnabled and Assigned(FOnTimer) then
    if SetTimer(FWindowHandle, 1, FInterval, nil) = 0 then
      raise EOutOfResources.Create(SNoTimers);
end;

procedure TTimer.SetEnabled(Value: Boolean);
begin
  if Value <> FEnabled then
  begin
    FEnabled := Value;
    UpdateTimer;
  end;
end;

procedure TTimer.SetInterval(Value: Cardinal);
begin
  if Value <> FInterval then
  begin
    FInterval := Value;
    UpdateTimer;
  end;
end;

procedure TTimer.SetOnTimer(Value: TNotifyEvent);
begin
  FOnTimer := Value;
  UpdateTimer;
end;

procedure TTimer.Timer;
begin
  if Assigned(FOnTimer) then FOnTimer(Self);
end;

Das erklärt allerdings nicht das Verhalten, dass man beobachten kann ggü. der Erwartung, dass Timermessages die MessageQueue "überfluten" sollten, wenn der Timer-Messagehandler sehr lange braucht. Scheint, als würde die API erst die nächste WM_TIMER-Message absetzen, wenn die WndProc das Ende der vorausgegangenen "gemeldet" hat. :idea: Das würde zumindest das beobachtete Verhalten erklären. :nixweiss: MSDN sagt leider nix dazu... :les: :lupe: :gruebel:

cu
Narses


jaenicke - So 16.12.12 02:47

Dazu muss man wissen, dass es intern mehrere Typen von Timern gibt (Oneshot-Timer, die nach Ausführung warten bis sie erneut gesetzt werden, RIT-Timer, die direkt wieder ausgeführt werden, egal was passiert, und normale Timer, die nach Ausführung auf ausführbar gesetzt werden).
Bei den Timern, um die es hier geht, wird das Flag beim Versuch den Timer auszuführen geprüft. (Wie prinzipiell auch bei den anderen.) Nur, wenn der Timer gerade bereit ist, also nicht ausgeführt wird, wird er erneut ausgeführt.

user profile iconNarses hat das auch am Ergebnis schon korrekt beschrieben.

Ein wenig zur Funktionsweise findet man hier:
http://www.reactos.freedoors.org/Reactos%200.3.12/ReactOS-0.3.12-REL-src/dll/win32/kernel32/string/ReactOS-0.3.12-REL-src/subsystems/win32/win32k/ntuser/timer.c
Auch wenn das auf den ersten Blick intern anders umgesetzt ist als in Microsoft Windows soweit ich das weiß.