关于Windows下定时器的使用

来源:互联网 发布:手机股票预警软件 编辑:程序博客网 时间:2024/05/15 23:52

在VC中,我写了不少应用级的软件,多数基于MFC来写代码。关于定时器的使用,在编码过程中,也会经常用到,现就常用的定时器作个比较,以方便大家使用。

常见的定时器有如下几种:CTimer、timeSetEvent和Socket通讯中的select

一、CTimer

CTimer的使用在MFC中最为常见,在基于CWnd的类中用起来简单、方便,缺点是必须要有窗口,可见的或不可见都可以。

使用方法如下:

1.开启定时器:

UINT SetTimer(
   UINT
nIDEvent,            //定时器ID
   UINT
nElapse,             //触发时间间隔,单位是毫秒。事实上,最小的的触发时间是50ms。最大的触发时间是USER_TIMER_MAXIMUM,即0x7FFFFFFF毫秒,即24.855天,如需每个月触发一次,建议自己另加一计数器,以实现更长时间的定时器
   void ( CALLBACK*
lpfnTimer )(HWND, UINT, UINT, DWORD) = NULL  //一般为NULL
) throw();

2.响应定时器:

afx_msg void OnTimer(           //触发时间到后的响应函数,消息ID是WM_TIMER   UINT_PTR nIDEvent              //定时器ID);
3.结束定时器:
BOOL KillTimer(                      //关闭定时器   UINT nIDEvent                      //定时器ID ) throw();

所以,用CTimer类来实现定时器时,有二点需注意:1.要有窗口,不能应用于服务软件的开发;2.定时的时间最好不要超过一天

 

二、timeSetEvent

timeSetEvent使用在多媒体中的应用较多,实时性也较好。使用方法如下:

1.初始化

MMRESULT timeGetDevCaps(          //得到系统的时钟步长信息
  LPTIMECAPS
ptc
  UINT
cbtc       
);

MMRESULT timeBeginPeriod(          //设置定时器的时钟步长
  UINT
uPeriod 
);

2.启动定时器

MMRESULT timeSetEvent(
  UINT         
uDelay,                         //定时器触发时长,单位是毫秒
  UINT
           uResolution,                  //定时器的时钟步长,单位是毫秒。如有可能,这个参数的时长应尽可能地长,以减少系统开销
  LPTIMECALLBACK
lpTimeProc,        //定时器触发时间到后的回调函数
  DWORD_PTR    
dwUser,                //用户自定义参数,当回调函数被调用时,dwUser将作为参数传入
  UINT
           fuEvent                         //定时器触发方式。可以是TIME_ONESHOT(只触发一次)、TIME_PERIODIC(可多次触发)
);

3.关闭定时器

MMRESULT timeKillEvent(
  UINT
uTimerID 
);

使用timeSetEvent来实现定时器时,理论上定时的时间最长也可以是24天,但实际在应用中会发现当时钟步长为50毫秒时,设的触发时长稍长情况下,有时会有不会触发定时器现象!有二点需注意:1.可以没有窗口,能应用于服务软件的开发;2.时钟步长为50毫秒时,定时的时间最好不要超过一分钟

下面是我们已实现的由timeSetEvent写就的CTimer代码,供大家参照:

 

// Timer.h: interface for the CTimer class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_TIMER_H__DB3A5421_33F9_4FB5_A4F6_22FB017020E3__INCLUDED_)
#define AFX_TIMER_H__DB3A5421_33F9_4FB5_A4F6_22FB017020E3__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "ArrayEx.h"

class CTimer
{
public:
 DWORD m_dwTimerPeriodMax;
 struct strTimerInfo
 {
  UINT uiTimerCount,
   uiTimerIndex;
  strTimerInfo()
  {
   ZeroMemory(this, sizeof(strTimerInfo));
  }
 };
 CMapUIntToUInt m_mapEventID2TimerInfo;
 CTimer();
 virtual ~CTimer();
 UINT SetTimer(UINT nElapse);
 BOOL KillTimer(UINT &nIDEvent);
 void KillAllTimer();
 virtual void OnTimer(UINT nIDEvent);
};

#endif // !defined(AFX_TIMER_H__DB3A5421_33F9_4FB5_A4F6_22FB017020E3__INCLUDED_)

 


// Timer.cpp: implementation of the CTimer class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Timer.h"
#include <Mmsystem.h>
#include "AutoLock.h"
#include "EventLog.h"

#pragma comment(lib, "Winmm.lib")

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CTimer::CTimer() : m_dwTimerPeriodMax(50)
{
 TIMECAPS  tc;
 if(timeGetDevCaps(&tc, sizeof(TIMECAPS)) == TIMERR_NOERROR)
 {
  //分辨率的值不能超出系统的取值范围
  m_dwTimerPeriodMax = tc.wPeriodMax;
  //调用timeBeginPeriod函数设置定时器的分辨率,类似于for循环的步长
  timeBeginPeriod(50);
 }
}

CTimer::~CTimer()
{
 KillAllTimer();
}

void CTimer::OnTimer(UINT nIDEvent)
{
 if( m_mapEventID2TimerInfo.PLookup(nIDEvent) )
 {
  TRACE("%s CTimer::OnTimer(nIDEvent=%u)/n", COleDateTime::GetCurrentTime().Format("%Y-%m-%d %H:%M:%S"), nIDEvent);
  DEBUGLOG_FORMAT(("CTimer::OnTimer(nIDEvent=%u)", nIDEvent));
 }
}

void __stdcall _TimerProc(UINT uiTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
 if( dwUser )
 {
  CTimer *pTimer = (CTimer *)dwUser;
  if( TRUE )
  {
   CreateAutoLock(pTimer->m_mapEventID2TimerInfo.m_Semaphore);
   CMapUIntToUInt::CPair *pTimerInfoPair = pTimer->m_mapEventID2TimerInfo.PLookup(uiTimerID);
   if( pTimerInfoPair )
   {
    CTimer::strTimerInfo *pTimerInfo = (CTimer::strTimerInfo *)pTimerInfoPair->value;
    if( ++pTimerInfo->uiTimerIndex<pTimerInfo->uiTimerCount )
     return;
    pTimerInfo->uiTimerIndex = 0;
   }
  }
  pTimer->OnTimer(uiTimerID);
 }
}

UINT CTimer::SetTimer(UINT nElapse)
{
 UINT nIDEvent = 0;
 if( nElapse<60000 )
  nIDEvent = timeSetEvent(nElapse, 50, (LPTIMECALLBACK)_TimerProc, (DWORD)this, TIME_PERIODIC);
 else
 {
  strTimerInfo *pTimerInfo = new strTimerInfo;
  if( nElapse>300000 )
  {
   pTimerInfo->uiTimerCount = nElapse/60000;
   nIDEvent = timeSetEvent(60000, 60000, (LPTIMECALLBACK)_TimerProc, (DWORD)this, TIME_PERIODIC);
  }
  else
  {
   pTimerInfo->uiTimerCount = nElapse/1000;
   nIDEvent = timeSetEvent(1000, 1000, (LPTIMECALLBACK)_TimerProc, (DWORD)this, TIME_PERIODIC);
  }
  if( nIDEvent==0 )
   delete pTimerInfo;
  else
  {
   CreateAutoLock(m_mapEventID2TimerInfo.m_Semaphore);
   m_mapEventID2TimerInfo[nIDEvent] = (UINT)pTimerInfo;
  }
 }
 TRACE("%s CTimer::SetTimer(nIDEvent=%u, nElapse=%u)/n", COleDateTime::GetCurrentTime().Format("%Y-%m-%d %H:%M:%S"), nIDEvent, nElapse);
 DEBUGLOG_FORMAT(("CTimer::SetTimer(nIDEvent=%u, nElapse=%u)", nIDEvent, nElapse));
 return nIDEvent;
}

BOOL CTimer::KillTimer(UINT &nIDEvent)
{
 if( (timeKillEvent(nIDEvent)==TIMERR_NOERROR) )
 {
  CreateAutoLock(m_mapEventID2TimerInfo.m_Semaphore);
  CMapUIntToUInt::CPair *pTimerInfoPair = m_mapEventID2TimerInfo.PLookup(nIDEvent);
  if( pTimerInfoPair )
  {
   strTimerInfo *pTimerInfo = (strTimerInfo *)pTimerInfoPair->value;
   delete pTimerInfo;
   m_mapEventID2TimerInfo.RemoveKey(nIDEvent);
  }
  TRACE("%s CTimer::KillTimer(nIDEvent=%u)/n", COleDateTime::GetCurrentTime().Format("%Y-%m-%d %H:%M:%S"), nIDEvent);
  DEBUGLOG_FORMAT(("CTimer::KillTimer(nIDEvent=%u)", nIDEvent));
  nIDEvent = 0;
  return TRUE;
 }
 else
  return FALSE;
}

void CTimer::KillAllTimer()
{
 //CreateAutoLock(m_mapEventID2TimerInfo.m_Semaphore);
 UINT uiKey = 0;
 strTimerInfo *pTimerInfo = NULL;
 POSITION Pos = m_mapEventID2TimerInfo.GetStartPosition();
 while( Pos )
 {
  m_mapEventID2TimerInfo.GetNextAssoc(Pos, uiKey, (UINT &)pTimerInfo);
  KillTimer(uiKey);
 }
 m_mapEventID2TimerInfo.RemoveAll();
}
上述代码可使用于桌面应用软件、服务软件等,但使用在桌面应用软件时,如定时器触发后需调用软件资源相关时,不宜直接调用函数,应该用SendMessage或PostMessage发送消息,由资源所在线程自行加载资源,否则会导致找不到资源的情况。

 

三、Socket通讯中的select

1.初始化

int WSAStartup(
  __in          WORD wVersionRequested,
  __out         LPWSADATA lpWSAData
);
2.利用SOCKET通讯的select的等待功能,建立定时器

int select(
  __in          int nfds,
  __in_out      fd_set* readfds,
  __in_out      fd_set* writefds,
  __in_out      fd_set* exceptfds,
  __in          const struct timeval* timeout
);

3.建立新的线程,定时器的触发时长由tv决定。例示如下:
 SOCKET hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 FD_SET fd = {1, hSocket};
 TIMEVAL tv = {10, 0};
 int i = select(0, &fd, NULL, NULL, &tv);
 closesocket(hSocket);

4.线程退出时,清理运行环境

int  WSACleanup (void);

select的使用和timeSetEvent有很相近的功效,只是需要自行建立线程,相对较麻烦。

原创粉丝点击