Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi TTimer und Abarbeitung der Messages (https://www.delphipraxis.net/182252-ttimer-und-abarbeitung-der-messages.html)

Blamaster 13. Okt 2014 18:29

TTimer und Abarbeitung der Messages
 
Hi,

ich habe mal eine kleine Frage zu der TTimer Umsetzung. Ich habe auf dem Hauptformular die TTimer Komponente gesetzt. Somit wird sich um die Freigabe automatisch gekümmert.

Jetzt kam es beim beenden des Programm zu einer Exception.


Delphi-Quellcode:
var
  myObject1: TMyObject;
  myObject2: TMyObject;
  myObject3: TMyObject;

procedure FormX.FormCreate(Sender: TObject);
begin
  myObject1:= TMyObject.Create;
  myObject2:= TMyObject.Create;
  myObject3:= TMyObject.Create;
end;


procedure FormX.Timer(Sender: TObject);
begin
  Button.Caption = myObject1.Status;
end;

procedure FormX.FormDestroy(Sender: TObject);
begin
 myObject1.Free;
 //              <-- Exception
 myObject2.Free;
 myObject3.Free;
end;
Der Debugger hat nun gezeigt das innerhalb des FormDestroy nach der Freigabe von myObject1 noch ein TimerEvent ausgelöst und abgearbeitet wird wenn myObject1 bereits freigegeben ist.

Das macht ja soweit bis zu einem gewissen Punkt auch Sinn. Allerdings bin ich bisher davon ausgegangen das die Messages im Programm lediglich im IdleState abgarbeitet werden. Das obige Verhalten widerspricht meiner Annahme. Wann werden die Messages nun wirklich abgarbeitet ?

Ein weiteres Problem. Theoretisch könnte man die Exception ja umgehen indem man im TimerEvent prüft ob myObject1 noch existiert. Alternativ dazu könnte man im FormDestroy bevor die Objekte freigegeben werden Timer.Enable := False setzen.

Allerdings befürchte ich das letzteres auch nicht wirklich zielführend ist. Wenn in einem unglücklichen Fall gerade eine Timer-Message in die Queue gelegt wurde und ich anschließend Timer.Enable := False setze könnte es dennoch passieren das ein Event nach dem Timer.Enable := false ausgeführt wird. Der von TTimer intern verwendete KillTimer() Befehl sorgt ja nicht dafür das bereits in der Queue stehende Messages entfernt werden.

Somit ist durch ein Timer.Enable := false nicht sichergestellt das nach dem Befehl keine Timer Events mehr ausgelöst werden.

Gibt es da eine saubere Möglichkeit um das Problem zu umgehen ?

teebee 13. Okt 2014 18:33

AW: TTimer und Abarbeitung der Messages
 
In FormDestroy:
Code:
FreeAndNil(MyObject1);
In Timer:
Code:
If Assigned(MyObject) Then...
Aber wahrscheinlich gibt es noch was eleganteres?

Sir Rufo 13. Okt 2014 18:36

AW: TTimer und Abarbeitung der Messages
 
Warum nimmst du einen Timer und nicht Delphi-Referenz durchsuchenTApplicationEvents.OnIdle? Würde hier besser passen.

Und ein
Delphi-Quellcode:
Assigned
verbunden mit einem
Delphi-Quellcode:
FreeAndNil
wirkt Wunder

Blamaster 13. Okt 2014 19:23

AW: TTimer und Abarbeitung der Messages
 
Hi,

vielen Dank für die schnellen Antworten :)

Der Weg über FreeAndNil() inkl Assigned() Prüfung ist mir bekannt. Allerdings ging es mit primär darum zu verstehen wie und wann die Window Messages abgerarbeitet werden das muss doch in irgendeiner Form deterministisch sein. Ich war immer der Auffassung das Messages im Gegensatz zu Threads nicht den Programmfluss unterbrechen und daher im Gegensatz zu Threads auch keine Synchronisation benötigen.

Desweiteren kommt mir die Nutzbarkeit des TTimer etwas arge eingeschränkt vor wenn man sich nicht darauf verlassen kann das nach einem .Enable := False; auch keine Events mehr ausgelöst werden.

Der Tipp mit dem IdleEvent ist super das muss ich mir merken :)

Nochmal zum TTimer:

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

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

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;
Das obige ist der Originalcode aus der ExtCtrls.pas würde ich mit folgender Änderung erreichen können das nach dem Disable wirklich keine Events mehr ausgelöst werden ? (oder übersehe ich da was ?)

Delphi-Quellcode:
procedure TTimer.WndProc(var Msg: TMessage);
begin
  with Msg do
    if Msg = WM_TIMER then
      try
        if FEnabled then Timer; // FEnable check to avoid Events after disable
      except
        Application.HandleException(Self);
      end
    else
      Result := DefWindowProc(FWindowHandle, Msg, wParam, lParam);
end;

himitsu 13. Okt 2014 19:56

AW: TTimer und Abarbeitung der Messages
 
Zitat:

t vor wenn man sich nicht darauf verlassen kann das nach einem .Enable := False; auch keine Events mehr ausgelöst werden.
Darauf kann man sich doch auch verlassen. :roll:

Wenn der Timer Disabled wird, dann wird sofort MSDN-Library durchsuchenKillTimer aufgerufen und damit werden auch alle TimerEvents aus der MessageQueue entfernt, womit auch kein WM_TIMER mehr eintreffen kann.

[add]
Und da man auf solche Komponenten niemals aus anderen Threads zugreift, kann es auch nicht zu Problemen kommen.
Auch nicht bezüglich des nachfolgend genannten
Delphi-Quellcode:
Timer.OnTimer := nil
, denn auch dabei wird sofort KillTimer aufgerufen. :stupid:

mm1256 13. Okt 2014 20:02

AW: TTimer und Abarbeitung der Messages
 
Und warum nicht einfach zuerst

Timer.OnTimer := nil

und dann erst

Timer.Enabled := false;

dann kann doch eigentlich nichts mehr passieren, oder?

Blamaster 13. Okt 2014 20:07

AW: TTimer und Abarbeitung der Messages
 
@himitsu
Genau das hätte ich für logisch empfunden.

Zitat aus der Windows Api
Zitat:

The KillTimer function does not remove WM_TIMER messages already posted to the message queue.
Sprich Messages die kurz vor dem Enable := false ankamen werden halt doch weiter verarbeitet und somit ist es durchaus realistisch das bei ungünstigem "scheduling" ein Event nach dem dsiable auftritt.

@mm1256
So sollte es natürlich auch klappen und man muss dafür das TTimer Objekt nicht anpassen perfekt.

himitsu 13. Okt 2014 20:39

AW: TTimer und Abarbeitung der Messages
 
Bist du sicher, daß die Hilfe da stimmt?

Ich kann mich nicht erinnern jemals ein TimerEvent nach dem deaktivieren eines Timers bekommen zu haben.
Da wäre es doch statistisch eigenartig, wenn das niemals passiert wäre. :gruebel:


Hab mal schnell einen Test gemacht:
> zwei Timer und Memo auf der Form

Delphi-Quellcode:
procedure TForm2.FormCreate(Sender: TObject);
begin
  Timer1.Interval := 500;
  Timer2.Interval := 5000;
end;

procedure TForm2.Timer1Timer(Sender: TObject);
begin
  Memo1.Tag := Memo1.Tag + 1;
  Memo1.Lines.Add('OnTimer: ' + IntToStr(Memo1.Tag));
end;

procedure TForm2.Timer2Timer(Sender: TObject);
begin
  Timer2.Enabled := False;

  Memo1.Lines.Add('Disable 1');
  //Timer1.Enabled := False; // disablen vor dem Event
  Sleep(1500);
  Timer1.Enabled := False; // disablen nach dem Event
  Memo1.Lines.Add('Disable 2');
end;
Delphi-Quellcode:
procedure TForm2.FormCreate(Sender: TObject);
begin
  Timer1.Interval := 500;
  Timer2.Enabled := False;
end;

procedure TForm2.Timer1Timer(Sender: TObject);
begin
  Memo1.Lines.Add('OnTimer');
  Sleep(1500);
  Memo1.Lines.Add('Disable');
  Timer1.Enabled := False;
end;

procedure TForm2.Timer2Timer(Sender: TObject);
begin
  //
end;
In beiden Fällen müsste dann ein OnTimer eintreffen, nachdem der Timer deaktiviert wurde, aber es passiert nicht. :gruebel:

himitsu 13. Okt 2014 20:54

AW: TTimer und Abarbeitung der Messages
 
:shock: Wenn ich manuell was in die Queue schiebe, dann wird das nicht verworfen.
Sehr verwirrend.

Delphi-Quellcode:
type
  THackedTimer = class(TComponent)
  private
    FInterval: Cardinal;
    FWindowHandle: HWND;
  end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  Timer1.Interval := 500;
  Timer2.Enabled := False;
end;

procedure TForm2.Timer1Timer(Sender: TObject);
begin
  Memo1.Lines.Add('OnTimer');

  if THackedTimer(Timer1).FInterval = 0 then ;
  PostMessage(THackedTimer(Timer1).FWindowHandle, WM_TIMER, 1, 0);

  Sleep(1500);
  Memo1.Lines.Add('Disable');
  Timer1.Enabled := False;
end;

procedure TForm2.Timer2Timer(Sender: TObject);
begin
  //
end;
Ich dachte Windows schiebt die Timer-Events asynchron in die Queue. :freak:

Blamaster 14. Okt 2014 11:51

AW: TTimer und Abarbeitung der Messages
 
Wirklich merkwürdig.

Was mich jetzt aber doch zusätzlich noch interessieren würde wie nun intern das abholen der Messages organisiert ist.
Es muss ja irgendwo im Hintergrund sowas wie eine MessageLoop geben. Nur unter welchen Voraussetzungen arbeitet die MessageLoop, sprich wann wird Sie abgearbeitet ?

Mich wundert ja noch immer das während sich das Programm im destructor befindet überhaupt noch Messages behandelt werden. Ich wäre jetzt erstmal davon ausgegangen das mit dem betreten des destructor automatisch keine weitere Messageverarbeitung mehr abgeholt werden.


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:03 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz