1. 程式人生 > >關於Windows下定時器的使用

關於Windows下定時器的使用

 

常見的定時器有如下幾種: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有很相近的功效,只是需要自行建立執行緒,相對較麻煩。