计时器和空闲处理!

 

       并非Microsoft Windows应用程序中的所有操作都是响应用户输入才执行的。有些操作处理本身是基于时间的,如“自动保存操作”以10分钟为间隔保存文档并更新在状态栏中显示的与时钟有关的项目。Windows提供了“计时器”帮助你编写定时发送消息的程序。另一种与时间有关的处理是“空闲处理”,在消息队列中没有消息等候的“空闲期间执行”。MFC以虚函数OnIdle的形式为空闲时间处理提供了一个主框架,每当CWinThread中的消息循环发现消息队列为空时就会调用该函数。

1、计时器

      使用计时器只需要了解2个函数。CWnd::SetTimer用于产生“以指定时间间隔发送消息的”计时器。CWnd::KillTimer消除计时器。根据传递给SetTimer函数的参数,计时器通过下面的2种途径通知应用程序时间间隔已到:

@给指定窗口发送WM_TIMER消息

@调用一个应用程序定义的回调函数

      WM_TIMER方式比较简单,但回调函数有时却更有用,特别是在使用了多个计时器时。这两种类型的计时器消息在发送给应用程序时具有较低的优先级别,只有在消息队列中没有其它消息时才处理它们。

      计时器消息永远不会积压在消息队列中。如果将计时器设置为每100毫秒产生一个消息,而在整整1秒内应用程序都忙于处理其它消息了,当消息队列清空后,它并不会突然接收到10个快速产生的消息。相反,它只接收到1条。Windows应用程序绝不应该用大量的时间处理一个消息,除非消息处理委派给了后台线程,因为那样的话如果主线程在长时间内不检查消息队列就会造成程序响应能力的下降。

1.1设置计时器:方法1

      设置计时器最简单的方法是用“计时器ID”和“计时器时间间隔”调用SetTimer函数,然后将WM_TIMER消息映射给OnTimer函数。计时器ID是唯一标识计时器的非0值。在响应WM_TIMER消息而激活OnTimer时,计时器ID作为OnTimer函数的一个参数。使用2个以上计时器的应用程序会使用计时器ID来标识产生特定消息的计时器。

      传递给SetTimer的“计时器间隔”指定了以毫秒为单位连续2次WM_TIMER消息之间的时间间隔。有效值从1ms到32位整数所能表示的最大值232-1ms,大概是49天半。语句:SetTimer(1,500,NULL);产生了一个计时器,其ID为1,它每500毫秒给窗口发送一个WM_TIMER消息,第三个参数NULL将计时器配置为使用OnTimer函数处理消息而不是用回调函数。虽然指定的时间间隔是500ms,但是实际窗口每550毫秒接收到一个消息。这是因为在大多数系统中(特别是基于Intel的系统)Windows计时器基于的硬件计时器或多或少54.9毫秒走一下,实际上Windows要将传递给SetTimer的值四舍五入到下一个55毫秒。因此,语句SetTimer(1,1,NULL);安排计时器大约55ms发送一个WM_TIMER消息,与SetTimer(1,50,NULL);相同。如果将间隔改为60msWM_TIMER就平均每110毫秒到达一次。

      因此,我们决不能依靠计时器进行类似码表那样的精确计时,如果编写一个时钟应用程序,安排计时器为1000ms间隔,并在每次WM_TIMER消息到达时更新显示,则不应该假定60个WM_TIMER消息就意味着逝去了1分钟,而应该在每次消息到达时都检查当前时间并更新时钟。这样的话,计时计时器消息流被打断,也会维持时钟的精确值。

      如果要编写精确计时的应用程序,可以使用Windows多媒体计时器,并将其设为1ms或更少的时间间隔。多媒体计时器提供了高精度计时功能,但它会产生额外开销并且对系统中运行的其它程序造成负面影响。

      如果SetTimer执行成功,返回值为计时器的ID,若失败则为0.在32位Windows系统中,系统可以支持的计时器数量实际上是无限的。虽然失败情况很少,但还是要谨慎,应检查返回值,以防系统资源不足(不要忘了,小疏忽能误大事,应用程序如果设置太多的计时器就会降低整个系统的运行效率)。由SetTimer返回的计时器ID与SetTimer函数第一个参数指定的计时器ID相同,除非参数指定为0,那样的话SetTimer将返回计时器ID为1.

      如果连续2次调研SetTimer(1,500,NULL); SetTimer(1,1000,NULL);会产生什么后果?如果给2个以上的计时器分配了相同的ID,SetTimer也不会失败。他会按要求分配 复制的ID。


1.2响应WM_TIMER消息

      MFC的ON_WM_TIMER消息映射宏将WM_TIMER消息映射给了类成员函数OnTimer。OnTimer的原型如下:

afx_msg void OnTimer(UINT nTimerID);nTimerID是产生消息的计时器的ID,可以在OnTimer中做“能够在其它消息处理函数中所做的”任何事情。包括捕获设备描述表以及绘制窗口。

      非常重要的一点:对于其它消息而言WM_TIMER消息不是被异步处理的。就是说,WM_TIMER消息永远不会中断同一个线程中的另一个WM_TIMER消息,也不会中断非计时器消息。WM_TIMER消息和其它消息一样在消息队列中等待被检索,在由消息循环检索调度之前不会得到处理。如果一个“定期处理函数”和“OnTimer”函数使用了同一个全局变量,只要这两个消息处理程序属于同一个窗口或是运行在同一个线程上的窗口,你就可以安全的假定对变量的访问不会重叠。

1.3设置计时器 方法2

      计时器并不是必须产生WM_TIMER消息。如果愿意,可以配置计时器在应用程序中调用一个回调函数而不是发送WM_TIMER消息。此方法通常 用在 使用了多个计时器的应用程序中,使得可以给每个计时器都分配唯一的处理函数。

      在Windows程序员中共同存在着一个误解,认为“回调函数类型的计时器”要比“WM_TIMER类型的计时器”效率要高。因为回调函数是直接由操作系统调用的而WM_TIMER消息要放置在应用程序的消息队列中。事实上,应用程序在调用调用::DispatchMessage之前对“回调函数类型的计时器”和“WM_TIMER类型的计时器”处理过程是一样的。在计时器开始运行之后,Windows会在消息队列中设置一个标志,用来表示计时器消息或回调函数是否在等待处理(开/关标志的特性就说明了为什么计时器消息不会在消息队列中堆积,在计时器时间间隔过去以后标志不会累加而只能设置为开关)。如果::GetMessage发现消息队列是空的并且没有窗口需要重绘,它就检查计时器标志。如果标志被设置了,::GetMessage就生产一个WM_TIMER消息,接下来由::DispatchMessage调度。如果产生消息的计时器时WM_TIMER类型的,消息就会调度到窗口去处理。如果已经注册了回调函数,::DispatchMessage就要调用回调函数了。所以“回调函数类型的计时器”并不比“WM_TIMER类型的计时器”有多少长处。

      要设置一个使用回调函数的计时器,可以在SetTimer函数的第3个参数中指定回调函数的名字,如下:SetTimer(1,100,TimerProc);在本例中被命名为TimerProc的回调函数的原型如下:

                    void CALLBACK TimerProc(HWND hWnd,UINT nMsg,UINT nTimerID,DWORD dwTime);

TimerProc中的hWnd参数保存窗口句柄,nMsg保存消息WM_TIMER,nTimerID保存计时器ID,dwTime指定Windows启动以后经过的毫秒数。回调函数应该是一个静态成员函数 或 全局函数,防止this指针传给它。

      在使用静态成员函数作为计时器回调函数时会遇到一个障碍,计时器处理过程中不像一些Windows回调函数那样可以接收到用户定义的lParam值。在计时器处理中,如果希望访问 非静态函数 和 数据成员 的话就必须采用其它的途径来获得指针。幸运的是,可以使用MFC的AfxGetMainWnd函数获取应用程序主窗口的一个指针。如果要想访问CMainWindow函数和数据成员,那么将返回值强制转换为CMainWindow指针是必要的。因为,AfxGetMainWnd返回的指针一般是CWnd指针。

CMainWindow* pMainWnd=(CMainWindow*)AfxGetMainWnd();一旦用这种方法初始化了pMainWnd,作为CMainWnd成员之一的TimeProc函数就可以访问非静态CMainWindow函数和数据成员了,就如同它自己是非静态成员函数一样。

1.4清除计时器

      与CWnd::SetTimer对应的是CWnd::KillTimer,它清除计时器并停止WM_TIMER_消息流或计时器回调函数。下面的语句释放ID为1的计时器:KillTimer(1);窗口的OnClose和OnDestroy是清除OnCreate创建的计时器的好地方。如果应用程序在结束之前没有释放计时器,32位Windows就会在处理结束之后清理它。但是好的编程风格要求每次调用SetTimer都应该配对的调用KillTimer以确保计时器资源被恰当的释放。

3、空闲处理

      由于是MFC的类CWinApp提供了消息循环来检索和调度消息,所以在没有消息等待处理时就在应用程序中调用一个函数对CWinApp来讲就很简单。看一下CWinThread::Run函数的源程序代码,该函数由WinMain调用来启动消息循环。

 

3.1使用OnIdle

      MFC应用程序可以重载从CWinApp继承来的OnIdle虚函数来指定空闲处理的任务。OnIdle函数原型如下:

                     virtual BOOL OnIdle(LONG lCount);

lCount是一个32位值,指定了从上次消息处理以来OnIdle被调用的总次数。计数值总是会连续增加直到CWinThread::Run中的消息循环调用PumpMessage检索和调度另一个消息为止。然后计数值重置为0再从头开始。WM_PAINT消息、WM_SYSTIMER消息和某些鼠标消息不会使lCount复位。lCount可以用来对上次消息调度后经过的时间,或叫做应用程序空闲的时间,进行大致的测量。

      如果你有2个后台任务希望在空闲时间执行,一个优先级别高、一个优先级别低,那你就可以安排优先级别高的任务每次lCount达到10时执行,而优先级别低的任务在lCount达到100甚至1000时执行。

      如果你能记录应用程序对OnIdle函数的调用次数,就会发现1000并不是很大的数字。通常,每秒会调用OnIdle函数100次或更多,一个lCount达到1000就启动的低优先级的后台任务一般会在鼠标和键盘闲置几秒后就得到执行。lCount达到10就执行的高优先级别任务会更频繁的得到执行,因为即使消息循环相当忙,计数值也经常达到或超过10.空闲处理应该尽可能的快速执行,否则在OnIdle返回之前消息传输就可能被堵塞。

      使用OnIdle的主要原则是 从重载版本中调用OnIdle的基类版本。如下,首先调用基类的OnIdle函数,在调用返回之后应用程序执行自己的空闲处理:

 

 实际上当lCountd等于0或1时是主框架在执行它的处理。因此让主框架处理程序有更高优先级别的方法是将自己的空闲处理延迟lCount到达2或者更高。

 

通过研究CWinApp::OnIdle和CWinThread::OnIdle的源程序代码,你可以了解MFC在空闲时间内所作的工作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值