2010-07-22 11 views
18

Ich habe einen TTimer in meiner Anwendung, der alle 2 Sekunden ausgelöst wird und ruft meine Ereignishandler, HandleTimerEvent(). Die Funktion "HandleTimerEvent()" ändert gemeinsam genutzte Ressourcen und kann vor ihrer Rückkehr 10 Sekunden dauern. Außerdem rufe ich Sleep() im Event-Handler auf, um den Prozessor zeitweise aufzugeben.Ist der Ereignishandler TTimer.OnTimer reentrant?

Ich bin nicht sicher, wie das TTimer-Objekt des C++ Builders beim Aufruf von Ereignissen funktioniert. Daher hat mich das gerade erläuterte Szenario vor allem daran erinnert, ob HandleTimerEvent() aufgerufen wird, bevor ein vorheriger Aufruf zurückgegeben wurde.

Die Frage kommt auf ein paar Dinge.

Stellt das TTimer-Objekt die Ereignisse in eine Warteschlange?

Kann das TTimer-Objekt meinen Event-Handler aufrufen, bevor ein vorheriger Aufruf zurückgegeben wurde?

Antwort

31

In dieser Antwort wird davon ausgegangen, dass TTimer weiterhin für die Verwendung von WM_Timer-Nachrichten implementiert ist. Wenn sich die Implementierung geändert hat (seit 2005), bitte ignorieren.

Nein, das TTimer-Objekt stellt keine Ereignisse in die Warteschlange. Es wird von der Windows WM_Timer-Nachricht gesteuert und Windows lässt WM_TIMER-Nachrichten nicht in der Nachrichtenwarteschlange aufstocken. Wenn das nächste Zeitgeberintervall auftritt und Windows erkennt, dass sich eine WM_Timer-Nachricht bereits in der Nachrichtenwarteschlange der Anwendung befindet, fügt es der Warteschlange keine weitere WM_Timer-Nachricht hinzu. (Gleiches für WM_Paint, btw)

Ja, es ist möglich, dass ein TTimer.OnTimer-Ereignis ausgelöst wird, auch wenn ein vorheriger Ereignishandler noch ausgeführt wird. Wenn Sie in Ihrem Ereignishandler etwas tun, mit dem die App Nachrichten verarbeiten kann, kann Ihr Timer-Ereignis erneut eingegeben werden. Der offensichtliche ist, wenn Ihr Event-Handler Application.ProcessMessages aufruft, aber es kann viel subtiler als das sein - wenn Sie Ihren Event-Handler aufrufen, ruft Application.ProcessMessages intern auf oder ruft PeekMessage/GetMessage + DispatchMessage auf oder öffnet einen modalen Dialog oder ruft eine COM-Schnittstelle auf, die an ein prozessexternes COM-Objekt gebunden ist, dann werden Nachrichten in Ihrer App-Nachrichtenwarteschlange verarbeitet, die Ihre nächste WM_Timer-Nachricht enthalten können.

Eine einfache Lösung besteht darin, das Timer-Objekt zu deaktivieren, wenn Sie den Timer-Event-Handler eingeben und ihn erneut aktivieren, wenn Sie den Timer-Event-Handler beenden. Dadurch wird verhindert, dass Timer-Meldungen ausgelöst werden, während der Ereignishandler noch funktioniert, unabhängig von den Eigenschaften der Nachrichtenbehandlung Ihres Codes.

+10

+1 zum Deaktivieren des Timers. Um die Effektivität der Deaktivierung des Timers zu demonstrieren (oder eine einfache Demonstration dessen, was schief gehen kann, wenn Sie dies nicht tun), zeigen Sie eine Nachrichtenbox im Timer-Handler an. Wenn Sie den Timer bei der Eingabe nicht deaktivieren, werden die Messageboxen zusammengelegt. –

+2

Sie können auch ein boolesches Flag verwenden, um die Rückkehr in den Timer-Event-Handler zu verhindern, aber das Deaktivieren des Timers selbst ist viel einfacher. – dthorpe

+0

Siehe https://forums.embarcadero.com/thread.jspa?messageID=171751𩻧 für eine nützliche TTimerGuard-Klasse, eine RAII-style-Klasse für TTimer-Verwendung. Möglicherweise müssen Sie die FInterval-Nutzung abhängig von Ihrer Implementierung anpassen. –

2

Ich verwende TTimer ausgiebig. Es werden keine Ereignisse in die Warteschlange gestellt. Wenn Sie möchten, dass es an einen Event-Handler übergeben wird, erstellen Sie ein TThread, das Ihre Ereignisse behandelt, damit der Timer mit seiner Arbeit fortfahren kann. Der Timer arbeitet nicht asynchron, sondern synchron.