Delphi 2010 - 实时准确地触发动作
我的第一个问题 - 如果不够具体,抱歉!
我自愿在德尔福为当地帆船俱乐部编写一个应用程序。每 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 classcode
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
要从另一个线程更新 VCL 控件,您必须同步并将线程的方法传递给它。
我自己从来不喜欢这个,耦合得太紧密了。
如果我使用线程执行此操作,我想我会使用共享内存操作,或者如果它很简单,只需一些 Windows 消息。
这是为灯和喇叭进行通信的线程???
To update VCL controls from another thread , you have synchronise and pass a method of your thread to it.
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???
如果您希望在很长一段时间内达到亚秒级精度,则需要确保您的 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.