运用Windows定时器队列实现高精度定时

更新时间:2019-10-03 来源:工程论文 点击:

【www.rjdtv.com--工程论文】

摘要

  0 引 言

  在软件开发过程中,定时器是一种常用的设计元素。Windows 平台上的定时器编程有多种实现方式,比较常用的有:SetTimer()实现的定时器、timeSetEvent()函数实现的多媒体定时器、可等待定时器 (Waitable timer)、相关的Windows API函数结合循环采用轮询机制实现的定时功能,以及定时器队列定时器(Timer-queue timer)等。

  SetTimer()函数可以为 Windows 程序分配一个定时器,该定时器利用WM_TIMER消息映射来进行简单的时间控制,定时精度约为55ms.由于Windows系统采用多线程的抢占式多任务工作方式,而 WM_TIMER消息优先级较低,因此这种定时方法适用于对定时精度要求不高的情况。Delphi、VB等快速原型化开发工具中自带的定时器控件,基本上都是采用该方法实现的。除了定时精度低以外,VB的定时器控件有更大的缺陷,其定时间隔不能超过65535ms,更限制了这样的定时器控件的应用。

  与SetTimer()函数实现的定时器相比,timeSetEvent()实现的多媒体定时器,可以实现多分辨率的定时,分辨率越高,定时精度越高,但系统所需的开销越重。该函数可以实现只进行一次性的定时事件出发,也可以进行常规的周期性定时,因此该方法的应用比较灵活,但出于种种原因,微软在MSDN中明确指出,该函数已被摒弃,在应用程序开发过程中不应再使用该方法进行定时。

  Windows 中很多与时间相关的 API 函数可以与循环结合,采用轮询机制实现定时功能。典型的API函数有GetTickCount()、 timeGetTime()、 QueryPerformanceFrequency() 和 QueryPerformanceCounter()。GetTickCount()和 timeGetTime()都是获取自系统启动以来流逝的秒数,结合循环,获取两次调用之间的差值即可实现定时。QueryPerformanceFrequency()和QueryPerformanceCounter()配合使用,QueryPerformanceFrequency()函数获取机器内部定时器的时钟频率,在需要定时的事件发生之前和发生之后分别调用QueryPerformanceCounter()函数,利用两次获得的计数之差,结合已获取的时钟频率,即可计算出两件事件之间经历的精确的时间长度,严格地来说,在Windows平台上,采用这两个函数实现的定时最为精确的,但由于需要结合循环进行轮询,因此在定时过程中需要不停地消耗CPU资源,是一种“忙等”机制。

  可等待定时器是Windows中的一种内核对象,因此该定时器的使用模式与其它内核对象的使用方式基本是一致的。CreateWaitableTimer()函数用于创建可等待定时器对象,SetWait ableTimer()函数设置等待的时长,WaitForSingleObject()函数等待该定时器对象有信号,如定时器对象有信号,则表明指定时长的时间已经流逝。作为一种内核对象,可等待定时器可以跨线程、进程使用,但在实际应用中,一般多用于线程同步或进程同步,而用于周期性定时的用法,并不多见。

  定时器队列定时器是微软在Windows2000及后续的Windows 操作系统中推荐使用的定时器,它支持多种工作模式,创建定时器队列定时器时,需指定一个回调函数,当定时时间到达时,自动调用回调函数实现周期性定时。顾名思义,这种定时器采用队列机制进行管理,是一种轻型对象,而且系统对回调函数采用线程池机制进行管理、调度,因此系统开销较小。

  从以上分析可知,使用定时器队列实现高精度定时具有明显的优越性,因此在Windows2000及后续的Windows操作系统中用来替代timeSetEvent()多媒体定时器。为了方便使用,本文使用Visual Basic6.0,利用定时器队列开发了一个ActiveX定时器控件,该控件拥有定时器队列定时器的一切优势,可以实现定时间隔最长为2147483647ms的高精度定时。

  1 定时器队列实现定时功能的基本原理

  如前所述,定时器队列定时器可以支持多种工作模式,其实现绕过了Windows消息队列机制,定时精度高。

  Windows 提供了一系列 API 函数用于创建、管理定时器队列及其中的定时器。使用定时器队列定时器,首先需要调用CreateTimerQueue()函数创建一个定时器队列,该函数无参数,成功调用时返回定时器队列的句柄,否则返回空,原型为:

  2 将定时器设计成 ActiveX 控件

  各种支持ActiveX控件的软件都有统一的接口,ActiveX控件在一种软件下开发而在其他软件中可以使用的控件,能极大增强软件的功能和提高代码复用的效率,本文运用上述定时器队列定时器的API函数,在简单高效的Visual Basic 6.0平台上,利用ActiveX技术将其开发成ActiveX控件,使其为工业控制和数据采集中的定时功能提供方便的服务。

  首先创建名为QTimer.ctlActiveX工程,将其设置为运行时不可见,即在属性窗口中将属性 InvisibleAtRuntime 设置为True,该定时器所用的几个 API 函数位于 kernel32.dll 中,先予以声明,在代码窗口中为ActiveX控件定义三个属性和一个事件,其中布尔型的变量m_Enabled是Enabled属性的值,表示定时控件是否可用,long型的m_Interval是定时器的时长以ms为 单 位 , 是 一 个 长 整 数 值 , 在 本 次 实 验 中 为 8 小 时 即28800000ms, m_FirstInterval 表示经过多长时间开始定时,Timer 是该控件的一个重要的事件,在定时时间到时由回调函数触发运行。

  当Enabled属性改变或者Interval属性值被修改时,并且控件处于运行状态,则要删除旧的定时器,当用户将Enabled属性False改变为True时,创建新的定时器,Enabled属性由True变为False时,删除定时器。关键代码如下:

  在上述代码中要用AddressOf 获取控件新的窗口过程地址和回调函数的入口地址,因此不能把窗口过程和回调函数写在ActiveX控件代码段内,需要在一个模块文件中编写。下面是新的窗口过程函数WindowProc,作用是处理控件窗口上的所有消息,其中最主要的是处理定时器的回调函数发来的消息,代码中用GetProp(hwnd, “QTimer”)读取QTimer属性的值,获取定时器的句柄,启动FireTime事件,窗口的其他消息仍然由原窗口过程处理,用 GetProp(hwnd, “ OldWinProc ”)读取OldWinProc 属性的值,获取原窗口过程入口地址,进行其他消息的处理。

  3 控件的测试

  将上述代码对应的ActiveX控件,编译后,注册到需定时的 工 程 控 制 程 序 中 进 行 测 试 , 将 Interval 值 被 设 置 为28800000,被用来进行每隔 8 小时循环定时,程序连续运行 24天,累计定时误差不超过30秒钟。

  4 总 结

  本文通过分析几种软件定时器的性能,经过实验,摒弃了其他几种方法,并且了解了他们的适用场合,最终选用定时器队列定时器,开发了ActiveX控件进行定时,能够进行超长高精度的定时功能,方便了用户在各种平台下的使用,目前在Windows XP和windows 7下注册测试并使用,效果良好。

  参考文献

  [1] 卓红艳,赵 平。 基于 VC_的实时数据采集系统中定时器的使用与比较[J]. 现代电子技术,2007年第18期。

  [2] 刘春风,田延岭。 Windows 操作系统下的软件定时器的设计与应用,机电一体化,2004年第5期。

  [3] 毕 业,史忠科。 Windows2000 下高精度定时器设计与实现[J]. 工业仪表与自动化装置,2007年第一期。

  [4] 李四保,姚晓先。 Win32s 下内核定时器的使用[J]. 微计算机信息,2003 年第 2 期。

  [5] 李 海。 Visual Basic 编程晋级:ActiveX 控件[M]. 北京航空航天大学出版社,2000年1月。

本文来源:http://www.rjdtv.com/gongchenglunwen/1481.html