title: Delphi 2010 - Triggering actions accurately in realtime
body: <p>My first question - apologies if it is not specific enough!</p>
  
  <p>I volunteered to write an app in Delphi for local sailing club. This fires various (RS232 commands) lights + Klaxon starting signals every 3 minutes, whole sequence can take 24 minutes. Since sailors set stopwatches this must be much better than 1 second over 24 minutes. </p>
  
  <p>I have a threaded timer component which IS accurate, and in <code>Timer.Execute</code> proc I need to update GUI etc - this is giving freezes/crashes etc. What is better way to do this? </p>
  
  <p>I think I should not be changing GUI objects in the execute, but how to get around it? (I am not very familiar with threads). Many thanks for you advice. Any further info needed I'm happy to give.</p>
  
  <p>Chris</p>
  
  <p>Addition - the CairnTimer class
  <code>code</code></p>
  
  <pre><code>unit CairnTimer;
  interface
  uses
    Windows,SysUtils,Classes,Dialogs;
  type
    TCairnTimer=class(TComponent)
    private
      TimerOn:             Boolean;
      TimerThreadPriority: TThreadPriority;
      TimerPaused:         Boolean;
      TimerDelay:          Cardinal;
      TimerResolution:     Cardinal;
      TimerTicks:          Cardinal;
      TimerMilliSeconds:   Cardinal;
      OnTimerEvent:        TNotifyEvent;
      OnTimerEventHandle:  Integer;
      TimerName:           Integer;
    protected
      procedure InitTimer;
      procedure SetTimerTicks(NewTicks: Cardinal);
      procedure UpdateTimerStatus(NewOn: Boolean);
      procedure UpdateTimerPriority(NewPriority: TThreadPriority);
    public
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;
      procedure Resume;
      procedure Pause;
      property Ticks: Cardinal read TimerTicks default 0;
      property MilliSeconds: Cardinal read TimerMilliSeconds default 0;
    published
      property Enabled: Boolean read TimerOn write UpdateTimerStatus default False;
      property TimerPriority: TThreadPriority read TimerThreadPriority write UpdateTimerPriority default tpNormal;
      property Delay: Cardinal read TimerDelay write TimerDelay default 100;
      property Resolution: Cardinal read TimerResolution write TimerResolution default 10;
      property OnTimer: TNotifyEvent read OnTimerEvent write OnTimerEvent;
    end;
  
    TCairnTimerThread=class(TThread)
    public
      CairnTimer: TCairnTimer;
      procedure Execute; override;
    end;
    TCairnTimerCallBack=procedure(NA1,NA2,CairnTimerUser,NA3,NA4: Integer) stdcall;
    ECairnTimer=class(Exception);
  
  var
    CairnTimerThread: TCairnTimerThread;
  
  procedure Register;
  
  implementation
  
  procedure Register;
  begin
    RegisterComponents('System',[TCairnTimer]);
  end;
  
  function KillTimer(CairnTimerName: Integer): Integer;stdcall;
             external 'WinMM.dll' name 'timeKillEvent';
  
  function SetTimer(TimerDelay,TimerResolution: Integer;
            CairnTimerCallBack: TCairnTimerCallBack;
            CairnTimerUser,CairnTimerFlags: Integer): Integer;stdcall;
            external 'WinMM.dll' name 'timeSetEvent';
  
  procedure TCairnTimerThread.Execute;
  var
    TickRecord: Cardinal;
  begin
    TickRecord:=0;
    while not(Terminated)and Assigned(CairnTimer)do
    begin
      WaitForSingleObject(CairnTimer.OnTimerEventHandle,INFINITE);
      Inc(TickRecord);
      CairnTimer.SetTimerTicks(TickRecord);
      if Assigned(CairnTimer.OnTimerEvent)then
        CairnTimer.OnTimerEvent(CairnTimer);
    end;
  end;
  
  constructor TCairnTimer.Create(AOwner: TComponent);
  begin
    inherited Create(AOwner);
    TimerOn:=False;
    TimerDelay:=100;
    TimerResolution:=10;
    TimerPaused:=False;
    TimerTicks:=0;
    TimerMilliSeconds:=0;
    TimerThreadPriority:=tpNormal;
    OnTimerEventHandle:=CreateEvent(nil,False,False,nil);
  end;
  
  destructor TCairnTimer.Destroy;
  begin
    Enabled:=False;
    CloseHandle(OnTimerEventHandle);
    inherited Destroy;
  end;
  
  procedure TCairnTimer.SetTimerTicks(NewTicks: Cardinal);
  begin
    TimerTicks:=NewTicks;
    TimerMilliSeconds:=TimerMilliSeconds+TimerDelay;
  end;
  
  procedure CairnTimerCallBack(NA1,NA2,CairnTimerUser,NA3,NA4: Integer); stdcall;
  var
    CairnTimer: TCairnTimer;
  begin
    CairnTimer:=TCairnTimer(CairnTimerUser);
    if Assigned(CairnTimer) then
      if not CairnTimer.TimerPaused then
        SetEvent(CairnTimer.OnTimerEventHandle);
  end;
  
  procedure TCairnTimer.InitTimer;
  begin
    TimerName:=SetTimer(TimerDelay,TimerResolution,@CairnTimerCallBack,Integer(Self),1);
    if TimerName=0 then
    begin
      TimerOn:=False;
      raise ECairnTimer.Create('Cairn timer creation error.');
    end;
  end;
  
  procedure TCairnTimer.UpdateTimerStatus(NewOn: Boolean);
  begin
    if NewOn=TimerOn then Exit;
    if (csDesigning in ComponentState) then
    begin
      TimerOn:=NewOn;
      Exit;
    end;
    if(NewOn)then
    begin
      CairnTimerThread:=TCairnTimerThread.Create(True);
      CairnTimerThread.CairnTimer:=Self;
      CairnTimerThread.FreeOnTerminate:=True;
      CairnTimerThread.Priority:=TimerThreadPriority;
      CairnTimerThread.CairnTimer.InitTimer;
      CairnTimerThread.Resume;
      TimerTicks:=0;
      TimerMilliSeconds:=0;
    end;
    if(not(NewOn))then
    begin
      KillTimer(TimerName);
      TerminateThread(CairnTimerThread.Handle,0);
      CairnTimerThread.Free;
    end;
    TimerOn:=NewOn;
  end;
  
  procedure TCairnTimer.UpdateTimerPriority(NewPriority: TThreadPriority);
  begin
    if NewPriority=TimerThreadPriority then Exit;
    if Assigned(CairnTimerThread) then
    begin
      CairnTimerThread.Priority:=NewPriority;
    end;
    TimerThreadPriority:=NewPriority;
  end;
  
  procedure TCairnTimer.Pause;
  begin
    if TimerOn then CairnTimerThread.Suspend;
    TimerPaused:=True;
  end;
  
  procedure TCairnTimer.Resume;
  begin
    if TimerOn then CairnTimerThread.Resume;
    TimerPaused:=False;
  end;
  
  end.
  </code></pre>