Implementing a timer

If you play with the demo application, you'll soon find that it works fine, just not exactly as I described before. I stated that "(the code) sends pingMsg back to the owner every five seconds." As the following image shows, that isn't exactly so.

This image shows one short testing session. During the testing, I started the thread. That caused two messages to be logged. The initial value of 0 is sent from the thread immediately when the thread is started. After that, the main thread sent a new value 42 to the worker thread, and that resulted in an immediate ping response.

After that, I didn't click anything for 11 seconds, which generated two ping messages, each sent 5 seconds after the previous message. Next, I clicked Change twice, which resulted in two ping messages. After that, I changed the value to 8, 9, and 0, and each time a ping was immediately received:

A proper description of the worker thread would therefore be this: It sends a message every 5 seconds, unless a new message is received, in which case it sends it back immediately.

If that's OK with you, fine! Feel free to use this framework. But sometimes you need a better timer. I could fix that in the TMyThread with a better calculation of the timeout parameter passed to the WaitFor. I would also have to fix the code, which now works the same, regardless of the WaitFor result (wrSignaled or wrTimeout). I could do that, but then you would have to repeat my steps each time you needed a thread with a built-in timer. Instead of that, I did something better and implemented a generic parent for all such tasks, a TCommTimerThread.

This time my implementation is closer to the approach used in all modern parallel libraries. It is not a multipurpose tool, such as TThread or TCommThread, but a specific solution that satisfies one usage pattern. Because of that, you don't have to implement any of the nasty multithreading plumbing to use it—such as when we had to implement a very specific Execute method in the previous example. Using a TCommTimerThread is more akin to using the VCL. You just plug in a few methods, and the framework does the rest. We'll see this approach a lot in the next chapter.

This approach has numerous advantages over the classical TThread. Firstly, it is easier to write the code, as all the hard stuff has already been taken care of. Secondly, the code will have less bugs, as you don't have to write error-prone multithreading plumbing. As many people are using the same framework, any potential bugs in the framework can be found quickly.

The usage pattern my implementation covers is "a background task that can send and receive messages and process timer-based events". While this looks simple, and even primitive, it can be used to solve many different kinds of problems, especially as you can ignore the message-processing part or the timer event part, if you don't need them.

Let us look at the implementation now. TCommTimerThread is derived from the TCommThread class so that it can reuse its communication mechanism. It exposes a public property Interval, which you can use to set the timer interval, in milliseconds. Setting  Interval to a value equal to or smaller than zero disables the timer. In addition to that, the code uses a FTimer: TStopwatch record to determine the time to the next timer event.

Instead of overriding the Execute function, you override four methods (or just some of them). Initialize is called immediately after the thread is created, and Cleanup is called just before it is destroyed. You can do your own initialization and cleanup inside them. ProcessTimer is called each time a timer event occurs, and ProcessMessage is called for each received message. The code fragment here shows all the important parts of the TCommTimerThread:

TCommTimerThread = class(TCommThread)
strict private
FInterval: integer;
FTimer: TStopwatch;
protected
procedure Cleanup; virtual;
procedure Execute; override;
procedure Initialize; virtual;
procedure ProcessMessage(const msg: TValue); virtual;
procedure ProcessTimer; virtual;
public
property Interval: integer read GetInterval write SetInterval;
end;

All important parts of the functionality are implemented in the overridden Execute method. It is very similar to TMyThread.Execute, except that the WaitFor timeout is calculated dynamically and that processing depends on the value returned from the WaitFor. If it returns wrSignaled, the code checks the Terminated flag and fetches waiting messages. If it returns wrTimeout, the timer is restarted, and the timer-handling function is called:

procedure TCommTimerThread.Execute;
var
awaited: TWaitResult;
timeout: Cardinal;
value: TValue;
begin
Initialize;
try
repeat
awaited := Event.WaitFor(CalculateTimeout);
event.ResetEvent;
case awaited of
wrSignaled:
begin
while (not Terminated) and GetMessage(value) do
ProcessMessage(value);
if Terminated then
break;
end;
wrTimeout:
begin
FTimer.Reset;
FTimer.Start;
ProcessTimer;
end
else
break; //Terminate thread
end;
until false;
finally
Cleanup;
end;
end;

The demonstration program implements TMyTimerThread, which uses this approach to implement the same functionality as the TMyThread. Instead of using a monolithic Execute, the code implements three very small overridden methods. Another change is that the local variable pingMsg is now a field, as it must be shared between different methods:

TMyTimerThread = class(TCommTimerThread)
strict private
FPingMsg: integer;
protected
procedure Initialize; override;
procedure ProcessMessage(const msg: TValue); override;
procedure ProcessTimer; override;
end;

The code to create this thread looks almost the same as the code that creates TMyThread, except that it also sets the interval between messages:

procedure TfrmThreadComm.btnStartTimerClick(Sender: TObject);
begin
FTimerThread := TMyTimerThread.Create(ProcessThreadMessages);
FTimerThread.Interval := 5000;
FTimerThread.SendToThread(42);
end;

Sending a message to the thread and stopping the thread looks completely the same as before. This thread also uses the same message receiver function in the main form, ProcessThreadMessage.

When a thread is initialized, the code sends a ping message to the owner. There's no need to initialize FPingMsg, as all of the object fields are initialized to zero when an object is created. The timer event handler, ProcessTimer, just calls the same helper function, SendPing, to send a message to the owner. And the message processing is trivial—it just sets the shared field:

procedure TMyTimerThread.Initialize;
begin
SendPing;
end;

procedure TMyTimerThread.ProcessMessage(const msg: TValue);
begin
FPingMsg := msg.AsInteger;
end;

procedure TMyTimerThread.ProcessTimer;
begin
SendPing;
end;

procedure TMyTimerThread.SendPing;
begin
if not SendToMain(TValue.From<TPingMsg>(
TPingMsg.Create(FPingMsg, ThreadID)))
then
raise Exception.Create('Queue full!');
end;

When the code is written in such way, it becomes clear why I prefer using patterns and communication over standard TThread code. There is no data sharing, no weird communication calls and, best of all, no mess. 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.8.91