關於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有很相近的功效,只是需要自行建立執行緒,相對較麻煩。