• 主页
  • 招聘
  • 语言基础/算法/系统设计
  • 数据库相关
  • 图形处理/多媒体
  • 网络通信/分布式开发
  • VCL组件开发及应用
  • Windows SDK/API

Timer并不简单,请大家进来讨论一下

godlessme 2005-03-24 09:29:52
昨天看到一本书中介绍了关于WM_TIMER消息的工作原理,结合我自己的理解阐述如下:
具体如何根据硬件的时钟滴答来发送TIMER消息,我不太了解,我只是说一下WINDOWS
对它的处理,WM_TIMER在消息队列中的优先级基本是最低的,并且在一个队列中同时只能有
一个timer消息。比如说你的timer每秒钟触发一次,并且如果这个timer事件的处理超过一秒钟的话,
例如3秒,那么在这三秒内应该触发三个timer消息,但是消息队列会忽略后两个timer消息,
只保留第一个,那么最后的结果好像是你的timer三秒钟才触发一次(这里假设你的程序中没有其他事件要处理,只有一个timer事件)。
举个例子,你在窗口中放一个timer组件和一个memo组件,timer一秒触发一次,然后在timer事件中加入这段代码:
sleep(1250);
memo1.lines.add(datetimetostr(now()));
那么你会发现memo中是按照这个规律显示时间:
2005-3-24 21:22:31
2005-3-24 21:22:32
2005-3-24 21:22:33
2005-3-24 21:22:35
2005-3-24 21:22:36
2005-3-24 21:22:37
2005-3-24 21:22:38
2005-3-24 21:22:40
2005-3-24 21:22:41
2005-3-24 21:22:42
2005-3-24 21:22:43
2005-3-24 21:22:45
2005-3-24 21:22:46
2005-3-24 21:22:47
2005-3-24 21:22:48
2005-3-24 21:22:50
2005-3-24 21:22:51
2005-3-24 21:22:52
2005-3-24 21:22:53
2005-3-24 21:22:55
注意一下后面的秒,有一定的规律,如果把时间精确到毫秒的话,那么可以看出其实是每隔
1.25秒显示一次时间。
我有一个疑问,如果你的timer事件的处理需要一定的时间,那么在段时间内,其他新加入队列的消息
就得不到及时的处理,程序看起来象死机一样,此时该怎办??调用processmessages??
...全文
457 点赞 收藏 34
写回复
34 条回复
Heyongfeng 2005年03月28日
多线程
回复 点赞
alexanda2000 2005年03月27日
学习一下.
回复 点赞
皮皮猪头 2005年03月27日
如果怕死机,那建议用多线程处理
回复 点赞
caiso 2005年03月27日
帮顶一下
回复 点赞
dezxp 2005年03月27日
up一下
回复 点赞
何鲁青 2005年03月26日
Delphi中三种延时方法及其定时精度分析 选择自 listenwind 的 Blog
关键字 Delphi中三种延时方法及其定时精度分析
出处

  在Delphi中,通常可以用以下三种方法来实现程序的延时,即TTtimer控件,Sleep函数,GetTickCount函数。但是其精度是各不相同的。

一、三种方法的简单介绍

1)TTtimer控件

  TTtimer控件的实质是调用Windows API定时函数SetTimer和KillTimer来实现的,并简化了对WM_TIMER 消息的处理过程。通过设置OnTimer事件和Interval属性,我们可以很方便的产生一些简单的定时事件。

2)Sleep函数

  Sleep函数用来使程序的执行延时给定的时间值。Sleep的调用形式为Sleep(milliseconds),暂停当前的进程milliseconds毫秒。Sleep的实现方法其实也是调用Windows API的Sleep函数。例如:

sleep(1000); //延迟1000毫秒

Sleep会引起程序停滞,如果你延迟的时间较长的话,你的程序将不能够响应延时期间的发生的其他消息,所以程序看起来好像暂时死机。

3)GetTickCount函数

  在主程序中延时,为了达到延时和响应消息这两个目的,GetTickCount()构成的循环就是一种广为流传的方法。例如:

procedure Delay(MSecs: Longint);
//延时函数,MSecs单位为毫秒(千分之1秒)
var
FirstTickCount, Now: Longint;
begin
FirstTickCount := GetTickCount();
repeat
Application.ProcessMessages;
Now := GetTickCount();
until (Now - FirstTickCount >= MSecs) or (Now < FirstTickCount);
end;

二、高精度的微妙级性能计数器(high-resolution performance counter)介绍

  为了比较以上方法的精度,首先需要找到一个参考的定时器。在这里,我提供了两个参考的定时器。一是用单片机每隔1.024ms产生一个实时中断RTI,作为计数器;二是选用了一个高精度的微妙级性能计数器(参见: http://msdn.microsoft.com/msdnmag/issues/04/03/HighResolutionTimer/default.aspx ,或者 http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=200249


1)计数器的Delphi源代码

{
A high-precision counter/timer. Retrieves time differences
downto microsec.
Quick Reference:
THPCounter inherits from TComponent.

Key-Methods:
Start: Starts the counter. Place this call just before the
code you want to measure.

Read: Reads the counter as a string. Place this call just
after the code you want to measure.

ReadInt: Reads the counter as an Int64. Place this call just
after the code you want to measure.
--------------------------------------------------------------------------------
}
unit HPCounter;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls;

type
TInt64 = TLargeInteger;
THPCounter = class(TComponent)
private
Frequency: TLargeInteger;
lpPerformanceCount1: TLargeInteger;
lpPerformanceCount2: TLargeInteger;
fAbout: string;
procedure SetAbout(Value: string);
{ Private declarations }
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Start;
function Read: string;
function ReadInt: TLargeInteger;
{ Private declarations }
published
property About: string read fAbout write SetAbout;
{ Published declarations }
end;


procedure Register;

implementation

procedure Register;
begin
RegisterComponents('MAs Prod.', [THPCounter]);
end;

constructor THPCounter.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
fAbout:= 'Version 1.1, 2000® Mats Asplund, EMail: masprod@telia.com, Site: http://go.to/masdp';
end;

destructor THPCounter.Destroy;
begin
inherited Destroy;
end;

function THPCounter.Read: string;
begin
QueryPerformanceCounter(TInt64((@lpPerformanceCount2)^));
QueryPerformanceFrequency(TInt64((@Frequency)^));
Result:=IntToStr(Round(1000000 * (lpPerformanceCount2 -
lpPerformanceCount1) / Frequency));
end;

function THPCounter.ReadInt: TLargeInteger;
begin
QueryPerformanceCounter(TInt64((@lpPerformanceCount2)^));
QueryPerformanceFrequency(TInt64((@Frequency)^));
Result:=Round(1000000 * (lpPerformanceCount2 -
lpPerformanceCount1) / Frequency);
end;

procedure THPCounter.SetAbout(Value: string);
begin
Exit;
end;

procedure THPCounter.Start;
begin
QueryPerformanceCounter(TInt64((@lpPerformanceCount1)^));
end;

end.

2)使用方法:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
HPCounter, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Label1: TLabel;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text:= '';
Application.ProcessMessages;
with THPCounter.Create(Self) do
begin
Start;
// Place code to measure here
Sleep(1000);
// Place code to measure here
Edit1.Text:=Read;
Free;
end;
end;

end.

二、三种方法的精度比较

  为了比较,采用以上3种方法,分别设置延时时间为1ms、2ms、5ms、10ms、20ms、50ms、100ms、200ms、500ms、1000ms,循环次数为5次,得到实际的延时时间。

1)TTtimer控件

实际延时时间(ms)
1ms: 8.012 21.551 6.875 21.647 9.809
2ms: 9.957 20.675 14.671 11.903 20.551
5ms: 9.952 20.605 9.924 20.705 12.682
10ms:14.852 9.96 21.547 9.82 20.634
20ms:27.512 34.291 26.427 31.244 30.398
50ms:61.196 61.307 64.027 62.048 63.059
100ms:102.495 108.408 112.318 110.322 102.531
200ms:193.955 202.135 207.016 205.082 202.194
500ms:496.659 500.534 503.398 495.551 500.394
1000ms:999.699 1003.576 993.698 1004.443 995.625

2)Sleep函数

1ms: 1.895 1.895 1.896 1.897 1.898
2ms: 2.868 2.874 2.852 2.872 2.869
5ms: 5.8 5.797 5.79 5.79 5.791
10ms:10.675 10.683 10.611 10.669 10.67
20ms:20.404 20.434 20.447 20.477 20.368
50ms:50.67 50.691 50.69 50.682 50.671
100ms:100.515 100.469 100.484 100.481 100.484
200ms:200.101 200.126 199.892 200.066 200.108
500ms:499.961 499.961 499.958 499.961 499.96
1000ms:1000.034 1000.04 1000.03 1000.018 1000.029

3)GetTickCount函数

1ms: 15.54 15.596 15.527 15.566 15.838
2ms: 15.561 15.563 15.603 15.477 15.571
5ms: 15.519 15.549 15.569 15.666 15.394
10ms:15.558 15.561 15.522 15.568 15.518
20ms:31.186 31.137 31.17 31.17 31.19
50ms:62.445 62.4 63.893 60.88 62.404
100ms:109.276 109.298 109.273 109.28 109.28
200ms:203.027 203.084 203.021 203.027 203.046
500ms:499.959 499.961 499.963 499.967 499.965
1000ms:1000.023 1000.022 1000.026 1000.029 1000.021


  可见,相对而言,Sleep的精度最高,尤其是在10ms以内的延时,只有sleep函数才能够做到。TTimer控件的定时精度最差,而且稳定性不好,波动很大。GetTickCount函数所能实现的最短延时为15ms左右,稳定性相对TTimer要好一些。
回复 点赞
flyinwuhan 2005年03月26日
呵呵,我的实验有问题。没有深入分析啊~~~~~~~~~~~~
看来“消息队列中在任何时候只保留一个wm_timer消息”这句话是对的^_^
回复 点赞
sc_arhat 2005年03月26日
up 一下
回复 点赞
flyinwuhan 2005年03月26日
>>千真万确,消息队列中在任何时候只保留一个wm_timer消息...不然的话,按照你的说法,消息处理是异步的了,那么就会存在这种情况:由于你程序或系统的繁忙,积攒了几十个timer消息,如果突然系统闲下来,那你这几十个timer就一个挨着一个触发timer事件,这样的话,timer岂不是不准时的太夸张了

你可以试验一下:
var
Form1: TForm1;
i : integer;
implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
i:=0;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
if i=0 then
sleep(3000);//休息3秒

if i>3 then
begin
timer1.enabled:=false;
exit;
end;
inc(i);
listbox1.items.add('xxx');//你的程序会停止3秒后突然连续触发3个timer事件!!
end;
回复 点赞
godlessme 2005年03月26日
niutuoshaozhe(牛拖少这) :
不管您是正常结束程序,还是调用application.terminate结束程序,结果都是在消息队列中放
个wm_quit消息,使fterminate属性为真,导致退出消息循环,结束程序。
但是消息循环只有在处理完timer事件之后,才能取到wm_quit消息,应该不会出现程序退出,timer还没有处理完的情况,除非你的程序有好几个消息循环。
回复 点赞
merkey2002 2005年03月26日
收藏了,有空再研究一下.
回复 点赞
godlessme 2005年03月26日
注:…………然的话,按照你的说法,消息处理是异步的了,那么就会存在这种情况:
由于你程序或系统的繁忙,积攒了几十个timer消息,如果突然系统…………

应该是:“timer消息处理是异步的了”
回复 点赞
godlessme 2005年03月26日
flyinwuhan(制怒·三思而后行)

>>比如说你的timer每秒钟触发一次,并且如果这个timer事件的处理超过一秒钟的话,
例如3秒,那么在这三秒内应该触发三个timer消息,但是消息队列会忽略后两个timer消息,
只保留第一个,那么最后的结果好像是你的timer三秒钟才触发一次

这话又是错的,你的程序不会忽略消息队列后两个timer消息,并且两个timer消息是在你的程序的
消息队列中的

千真万确,消息队列中在任何时候只保留一个wm_timer消息(当然,如果你定义了两个timer,那就保留两个wm_timer消息。不然的话,按照你的说法,消息处理是异步的了,那么就会存在这种情况:
由于你程序或系统的繁忙,积攒了几十个timer消息,如果突然系统闲下来,那你这几十个timer就一个挨着一个触发timer事件,这样的话,timer岂不是不准时的太夸张了。
回复 点赞
godlessme 2005年03月25日
flyinwuhan(制怒·三思而后行) :
我的理解是windows会按照您设定的时间间隔准时的给你发送timer消息,但是如果您的程序处理其他的消息过长,期间windows给你的消息队列发送了数个timer消息,但是队列只保留一个,而忽略其他的。
结果造成两个timer事件的处理间隔不到一秒,也就是说timer是准时的,但是处理事件并不准时。
是这样吧?
回复 点赞
ffwin 2005年03月25日
同意楼上
回复 点赞
skertone 2005年03月25日
TTimer 精确度在 50 - 49 ms 之间 Windows2K 比 Win98 稍准一点

你要实现小于50ms的计时用TTimer是不可能实现滴 楼上多提到的多嫖体定时器应用

procedure TimeCallBack(uTimerID, uMessage: UINT;dwUser, dw1, dw2: DWORD); stdcall; //计时器回调函数
procedure TimeCallBack(uTimerID, uMessage: UINT;dwUser, dw1, dw2: DWORD);
begin
PostMessage(dwUser,CM_MARQUEE,dwUser,uTimerID);
end;

procedure TSupperLED.SetHPTimer;
var
pTP: PTimeCaps;
begin
if FTimeID <> 0 then DeleteHPTimer;

GetMem(pTP,SizeOf(TTimeCaps));
try
timeGetDevCaps(pTP,SizeOf(TTimeCaps)); //取当前系统最高计时精度能力
timeBeginPeriod(pTP^.wPeriodMin); //设置计时精度为最佳
finally
FreeMem(pTP,SizeOf(TTimeCaps));
end;
{传入接收消息窗口句柄备用}
FTimeID := timeSetEvent(1000 - FSpeed,1,@TimeCallBack,FWindowHandle,TIME_PERIODIC) //设置高精度计时器
end;
回复 点赞
ly_liuyang 2005年03月25日
Timer是不准时的!
想可靠就用多媒体定时器
我在FAQ中提过的,http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=200249

http://lysoft.7u7.net
回复 点赞
flyinwuhan 2005年03月25日
>>timer在消息队列中的等级是最低的,即便不用sleep,使用其他方法使该timer事件执行时间超过1秒,或者程序中处理其他的消息超过一秒,那么结果也会是1秒钟的timer不会真正的一秒触发一次。
是不是这样??
---肯定的啊

>>有没有其他的办法使计时器能够更加精确??
---多媒体计时器

>>timer事件的触发间隔是完全无规律的。。。甚至两个timer事件的时间间隔比你定义的还要短
---错!
procedure TForm1.Timer1Timer(Sender: TObject);
...
begin
boolFlag := true;
while boolFlag do
begin
...
if iInterval mod 2 = 0 then
Sleep(1600)//你sleep了,所以你得程序不会处理(响应)任何消息了~~~
//但是系统还是按时给你发送了一个timer消息,可是你在睡觉,不响应它,
//当你醒过来处理这个消息的时候,时间已经过去了
//1600-1000=600ms,所以这次的间隔是1600ms;
else
Sleep(200);//你又sleep了,但是系统按时给你发送了一个timer消息,这次的是在你醒来200ms后 //后接到的,所以sleep的200ms+200ms=400ms....一切都是很有规律的啊!!!~
boolFlag := false;
end;
end;

回复 点赞
godlessme 2005年03月25日
最新发现,timer事件的触发间隔是完全无规律的,甚至两个timer事件的时间间隔比你定义的还要短,有
程序为证:
定义两个全局变量:
iInterval: integer;
dtBegin: TDateTime;

timer事件如下:
procedure TForm1.Timer1Timer(Sender: TObject);
var
boolFlag: boolean;
dtCur: TDateTime;
h,m,s,ms: Word;
begin
boolFlag := true;
while boolFlag do
begin
iInterval := iInterVal + 1;
dtCur := Now();
DecodeTime((dtCur - dtBegin),h,m,s,ms);
Memo1.Lines.Add(Inttostr(s*1000 + ms));
dtBegin := dtCur;
if iInterval mod 2 = 0 then
Sleep(1600)
else
Sleep(200);
boolFlag := false;
end;
end;

结果的规律如下:
1002
1602
401
1602
401
1602
400
1603
400
1603
400
1603
400
1602
401
1602
401
1602
401
1602
401
1602

这表明我定义1秒触发一次的timer,由于我的timer事件执行时间过长,导致两个timer事件之间间隔
小于1秒
回复 点赞
caiso 2005年03月25日
顶一下
回复 点赞
发动态
发帖子
Delphi
创建于2007-08-02

1472

社区成员

26.2w+

社区内容

Delphi 开发及应用
社区公告
暂无公告