在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 ); |
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有很相近的功效,只是需要自行建立线程,相对较麻烦。