Search code examples
delphitthread

How to call methods of a thread from a thread?


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.


Solution

  • 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.