【问题标题】:Delphi 2010 - Triggering actions accurately in realtimeDelphi 2010 - 实时准确地触发动作
【发布时间】:2012-05-05 01:18:09
【问题描述】:

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

我自愿在 Delphi 中为当地的帆船俱乐部编写了一个应用程序。这会每 3 分钟触发各种(RS232 命令)灯 + Klaxon 启动信号,整个序列可能需要 24 分钟。由于水手设置了秒表,因此这必须比 24 分钟内的 1 秒好得多。

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

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

克里斯

加法 - CairnTimer 类 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.

【问题讨论】:

  • 欢迎来到 StackOverflow。如果没有任何代码来显示您在做什么,很难说出您可能做错了什么。你用的是什么线程定时器?您是否正在使用 Synchronize 更新 GUI?如果没有,您将如何尝试更新它?请编辑您的问题以提供更多信息(最好以某些代码的形式),以便更清楚您当前正在做什么。 (另外,freezes/crashes/etc. 是对错误或问题的毫无意义的描述;它也没有提供太多信息。)
  • 也许[delphi] update gui thread搜索中的一个问题对你有一些有用的信息。
  • 我正在使用一个名为 CairnTimer 的免费软件组件。我没有使用同步。线程计时器执行如下所示:
  • 过程 TCairnTimerThread.Execute; var TickRecord:红衣主教;开始 TickRecord:=0;而不是(终止)和分配(CairnTimer)开始 WaitForSingleObject(CairnTimer.OnTimerEventHandle,INFINITE);公司(滴答记录); CairnTimer.SetTimerTicks(TickRecord);如果已分配(CairnTimer.OnTimerEvent)则 CairnTimer.OnTimerEvent(CairnTimer);结尾;结束;
  • 为什么需要用线程来做这个?我看不出他们有什么帮助。相反,线程可能只会使事情复杂化

标签: delphi timer


【解决方案1】:

要从另一个线程更新 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 消息。

是线程在为灯和喇叭做通讯???

【讨论】:

  • 我正在使用 TMS Async 组件发送 RS232 命令,但在 timer.OnTimer 执行过程中调用它。如果我能弄清楚如何发布计时器代码,我会这样做! (帮助!!)
  • @Ken 已发布代码作为原始问题的补充,非常感谢您迄今为止的帮助
  • 这里有太多其他可能导致问题的东西。如果我是你,我会用一个直接的 TTimer 组件从主线程中工作。只要您的主线程不忙于其他事情,它就应该在一秒钟内轻松处理 OnTimer 事件。一个你已经开始工作的人(假设你编写了模块化代码),你可以看看线程化它,虽然像@Andrew Hefferenan 一样,我看不出有什么特别的理由这样做。多线程提示一,确保你想要线程的工作。
  • 使用 TTimer 我的应用似乎没问题! (当然不准确)。所以我想知道,代码中的 Notify 事件是否意味着我有我的代码的 CairnTimer.OnTimer() 是在 Timer 线程还是在主 VCL 线程中运行?我是否应该简单地更新一个全局变量(在关键部分?),然后使用单独的 TTimer 以 0.2 秒的间隔“轮询”它以进行更改,并从中更新 GUI 等(即在主线程中)你是什么认为?
  • 线程引发了事件,因此事件处理程序中的任何代码也在线程中。这就是为什么您需要同步或切换之类的东西。只要计时器在主线程中运行,您的最后一个想法就会起作用。不过,Windows 消息将需要明确的轮询。
【解决方案2】:

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

【讨论】:

  • -1。这与提出的问题无关,它明确表示I have a threaded timer component which IS accurate
  • @Ken,精确到什么,PC 还是 NTP 服务器?你问过吗?我很乐意接受投反对票来发布我认为有用的信息。
  • @Ken,我建议你阅读social.msdn.microsoft.com/Forums/is/vcgeneral/thread/…(所有关于 SetTimer() 和 PC 时钟精度问题)
  • 那家伙用秒表。大概是 Klaxon 到 Klaxon,所以这个持续时间,不是在格林威治标准时间 15:08 准确地这样做
  • @Tony,持续时间也受 PC 时钟漂移的影响。如果时钟在 1 小时内漂移 3 秒,那么超过 24 分钟,持续时间将超过 1 秒。保持 PC/笔记本电脑同步可确保您将持续时间保持在接近毫秒的精度。我通过睡到设定时间(使用同步时钟)并每 100 毫秒左右检查一次来做到这一点。当不到 100 毫秒时,您会在剩余的时间内睡眠。通过这种方式,我可以让计时器跨度数天,并且精确到几毫秒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-09
  • 2021-12-24
  • 1970-01-01
  • 2012-07-20
  • 1970-01-01
  • 2011-11-18
  • 1970-01-01
相关资源
最近更新 更多