I just learned that creating and using a Vcl.TTimer
from a worker thread is not thread safe. My worker thread works fine 'most of the time' using the Vcl.TTimer
. I found a class TTimerThread
based on events which works fine also, but I don't know how to combine both classes that TTimerThread
has access to the methods of my worker thread.
ExecuteTimed()
will be called when the event occurs (3000ms).
Pseudo code:
uses TimerThread;
type
TMyTimerThread = class( TTimerThread )
protected
procedure ExecuteTimed; override;
[...]
TMQTTClient = class(TThread)
private
[...]
fKeepAliveTimer:TTimer; // Works, but is ugly.
fKeepAliveTimerThread:TMyTimerThread; //
[...]
procedure TMyTimerThread.ExecuteTimed;
begin
beep; // Works fine!
end;
[...]
constructor TMQTTClient.Create(aHostname: string; aPort: integer; ClientID:String;aKeepAliveSeconds : word);
begin
//init some stuff...
fKeepAliveTimer:=TTimer.Create(nil); // ugly
fKeepAliveTimer.Interval:=fKeepAliveSeconds * 1000;
fKeepAliveTimer.OnTimer:=DoKeepAlive; // The function pointer which should be called ~ every xxxx ms. Works fine, but no thread safe.
fKeepAliveTimerThread:=TMyTimerThread.Create();
fKeepAliveTimerThread.Interval:=3000;
//fKeepAliveTimerThread.ExecuteTimed := ?; //here I wish to assign a pointer to the ExecuteTimed method of MyTimerThread.
I could imagine solving it using TThread.Synchronize()
in ExecuteTimed()
which calls the instance of the worker thread like frmMain.myMqttWorkerClass.DoKeepAlive
, but I am in doubt if this is the right way.
You can add a public event to TMyTimerThread
and have TMQTTClient
assign your DoKeepAlive
handler to it, just like you did with the TTimer
, eg:
type
TMyTimerThread = class(TTimerThread)
private
fOnTimer: TNotifyEvent;
protected
procedure ExecuteTimed; override;
public
property OnTimer: TNotifyEvent read fOnTimer write fOnTimer;
end;
TMQTTClient = class(TThread)
private
[...]
fKeepAliveTimerThread: TMyTimerThread;
procedure DoKeepAlive(Sender: TObject);
[...]
end;
...
procedure TMyTimerThread.ExecuteTimed;
begin
if Assigned(fOnTimer) then fOnTimer(Self);
end;
...
constructor TMQTTClient.Create(aHostname: string; aPort: integer; ClientID:String;aKeepAliveSeconds : word);
begin
//init some stuff...
fKeepAliveTimerThread := TMyTimerThread.Create();
fKeepAliveTimerThread.Interval := 3000;
fKeepAliveTimerThread.OnTimer := DoKeepAlive;
end;
Just keep in mind that DoKeepAlive
will be called in the context of the timer thread, so make sure that anything it does is thread-safe.