Delphi 2010 - 实时准确地触发动作

发布于 2025-01-06 00:20:36 字数 5151 浏览 1 评论 0原文

我的第一个问题 - 如果不够具体,抱歉!

我自愿在德尔福为当地帆船俱乐部编写一个应用程序。每 3 分钟就会发出各种(RS232 命令)灯 + 高音喇叭启动信号,整个序列可能需要 24 分钟。由于水手们设置了秒表,这肯定比 24 分钟内的 1 秒要好得多。

我有一个准确的线程计时器组件,在 Timer.Execute 过程中我需要更新 GUI 等 - 这会导致冻结/崩溃等。有什么更好的方法来做到这一点?

我认为我不应该在执行中更改 GUI 对象,但如何解决它? (我对线程不是很熟悉)。非常感谢您的建议。如果需要任何进一步的信息,我很乐意提供。

Chris

Addition - CairnTimer 类 代码

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.

My first question - apologies if it is not specific enough!

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.

I have a threaded timer component which IS accurate, and in Timer.Execute proc I need to update GUI etc - this is giving freezes/crashes etc. What is better way to do this?

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.

Chris

Addition - the CairnTimer class
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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清风无影 2025-01-13 00:20:36

要从另一个线程更新 VCL 控件,您必须同步并将线程的方法传递给它。

procedure TMyThread.DoProgress;
 var
   PctDone: Extended;
 begin
   PctDone := (FCounter / FCountTo) ;
   FProgressBar.Position := Round(FProgressBar.Step * PctDone) ;
   FOwnerButton.Caption := FormatFloat('0.00 %', PctDone * 100) ;
 end;

 procedure TMyThread.Execute;
 const
   Interval = 1000000;
 begin
   FreeOnTerminate := True;
   FProgressBar.Max := FCountTo div Interval;
   FProgressBar.Step := FProgressBar.Max;

   while FCounter < FCountTo do
   begin
     if FCounter mod Interval = 0 then Synchronize(DoProgress) ;

     Inc(FCounter) ;
   end;

   FOwnerButton.Caption := 'Start';
   FOwnerButton.OwnedThread := nil;
   FProgressBar.Position := FProgressBar.Max;
 end;

我自己从来不喜欢这个,耦合得太紧密了。

如果我使用线程执行此操作,我想我会使用共享内存操作,或者如果它很简单,只需一些 Windows 消息。

这是为灯和喇叭进行通信的线程???

To update VCL controls from another thread , you have synchronise and pass a method of your thread to it.

procedure TMyThread.DoProgress;
 var
   PctDone: Extended;
 begin
   PctDone := (FCounter / FCountTo) ;
   FProgressBar.Position := Round(FProgressBar.Step * PctDone) ;
   FOwnerButton.Caption := FormatFloat('0.00 %', PctDone * 100) ;
 end;

 procedure TMyThread.Execute;
 const
   Interval = 1000000;
 begin
   FreeOnTerminate := True;
   FProgressBar.Max := FCountTo div Interval;
   FProgressBar.Step := FProgressBar.Max;

   while FCounter < FCountTo do
   begin
     if FCounter mod Interval = 0 then Synchronize(DoProgress) ;

     Inc(FCounter) ;
   end;

   FOwnerButton.Caption := 'Start';
   FOwnerButton.OwnedThread := nil;
   FProgressBar.Position := FProgressBar.Max;
 end;

Never liked this one myself, too tightly coupled.

If I was doing this with threads, I think I'd be using a shared memory manouevre or if it was simple just a few windows messages.

It is the thread that's doing the comms for the light and horns???

2025-01-13 00:20:36

如果您希望在很长一段时间内达到亚秒级精度,则需要确保您的 PC 时钟准确。我见过 PC 时钟在一小时内会漂移几秒钟,因此完全准确的计时器仍然不够。为此,我每分钟将我的 PC/笔记本电脑时钟与 NTP 服务器同步(取大约 20 个请求的中值/平均值)。因此,良好的服务器/PC/笔记本电脑可以始终将准确的参考时间保持在几毫秒之内。

If you want sub-second accuracy over a lengthy period of time you need to ensure that your PC clock is accurate. I have seen PC clocks that can drift a couple of seconds over an hour, so a completely accurate timer will still not suffice. To do this I synchronise my PC/laptop clocks with an NTP server every minute (taking the median/average of about 20 requests). A good server/PC/laptop can thus be kept within a few miliiseconds of an accurate reference time all the time.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文