技术资料_浅谈定时器组件TTimer和高精度定时

浅谈定时器组件TTimer和高精度定时
CnPack开发组 与月共舞
2002.04.29

用Delphi写程序的朋友对TTimer组件应该是比较熟悉的了,但也许很少有朋友对它有过深入的了解,这份文档打算简单地介绍一下Windows下定时器的一些有趣的东西,同时也作为CnPack中TCnTimer高精度定时器组件的介绍。

TTimer的实现代码在VCL源码的ExtCtrls单元中。从组件的源码中,我们知道它内部使用SetTimer这个API函数来调用操作系统提供的定时器功能。

SetTimer需要提供四个参数:窗口句柄、定时器ID、定时间隔和回调函数入口。操作系统定时向该窗口消息队列中发送WM_TIMER消息,并将定时器ID和回调函数入口做为消息参数传递。

TTimer在构造器中使用Forms单元提供的AllocateHWnd过程创建了一个隐藏的窗口并在析构方法中用DeallocateHWnd释放。创建窗口时它提供了一个方法指针WndProc用于处理窗口消息,该方法只是简单地响应WM_TIMER消息并产生OnTimer事件。当定时间隔大于0、Enabled为真、定义了OnTimer事件时,TTimer调用SetTimer进行定时。

了解了TTimer的工作机制后,我们来看看它的一些不足:

  1. 首先,每一个TTimer实例都会创建一个窗口,我们知道窗口是会消耗系统资源的,特别是在Win9X下,当用户资源或GDI资源用完时,死机就是不可避免的了。其实只需要一个窗口,根据不同的定时器ID就可区分了。Rxlib中有一个组件叫RxTimerList,它就是只使用了一个窗口句柄和一个定时器ID,通过特别的算法实现多个定时器的功能,不过它的定时精度较低。
  2. 从表面上看,TTimer的定时分辨率是1ms,Win32 SDK中也没有提及内部定时器精度问题。事实上,很多朋友都知道,Win9X下它的实际分辨率是55ms,而NT/2000下是10ms左右。对TTimer来说,将Interval属性设置为1和55效果是一样的,写一个很简单的程序就可证明(Win9X下)。

很多书上提到Win9X下定时器的实际精度是55ms,事实真的就是这样吗?

我在实测中发现一些有趣的现象(以下描述都在Win98SE下):

  1. 将定时间隔设为1ms,用一段代码在窗体上显示实际的定时间隔。
  2. 运行程序,我发现定时间隔在55ms左右,每秒约18次。
  3. 而当我快速移动鼠标或持续按住某个键时,实际间隔最短变化到20ms左右,每秒可达数十次。
  4. 当我在程序中加入一段串口通讯代码,并通过外部硬件向串口连续发数时,定时器速度居然达到了数百次。

55ms的定时间隔,让我很容易地联想到DOS下的外部定时中断INT08,再看看键盘中断INT09、串口中断INT0C,很自然地得出这样的结论:

Win9X是在响应外部中断时处理定时器的,正常情况下INT08定时中断的产生频繁最高,所以表现出的实际定时精度为55ms。

为了证明这一想法,我写了一个简单的程序:

  • 在窗体上放两个TTimer,定时间隔为55ms,Enabled为假,再放一按钮,四个标签。
  • 在定时事件中将GetTickCount(从开机到当前经过的毫秒数)的值显示出来,再将定时器关掉。
  • 在按钮事件中先将第一个定时器打开,显示当前时间,用Sleep延时25ms,再将第二个定时器打开,显示当前时间。

执行结果可看到,虽然两个定时器开启的时间相差了27ms,但两个定时事件发生在同一时间段内(显示值完全相同)。

附:时间有限,先写到这,下半部分将接着叙述:

  • Win32的抢占式多任务系统决定了定时器的最终精度。
  • 线程方式定时的实现和优劣。
  • MIDI序列定时器的介绍。
  • 其它的定时方式。
  • CnPack中TCnTimer定时器的介绍。
CNPACK开发中心