c++ 定时器
场景:C++工具种——定时器
C++工具类——定时器
为什么我们要将 timerID 用 void* 表示呢?由于当定时器设置过多时,这个 ID 有可能重复,所以我们用一片内存的首地址来表示,尽量减小定时器的 ID 重复的可能性。我们还间接通过定时器实现了一个异步调用 AsyncCall 。
我们在 CTimerMgr 内部维护了一个定时器信息的 map。该 map 用定时器的 ID 做键值。在 CTimerMgr 内实现了一个隐藏在内部的Windows 窗口,所有的计时器消息将送往该窗口的 WM_TIMER 处理函数。看起来没有一点难度。但功能非常强大:
C++工具类——定时器
有时候我们需要用到定时器这样一个东西,但是我们如果去一个窗口里面 SetTimer,但我们又需要在一个非 UI 类(线程)里要用计时器,那么解耦就没有办法实现了。有没有更好的办法呢?
答案是肯定的,我看可以写一个单件定时器类,用来管理定时控制,并且全局访问。你可能需要的知识有:单件模板类、Boost 等。我们期望的使用方式是:
/** * \file timer.h * \author arnozhang * \date 2012.8.13 * \brief 系统计时器模块. */ #ifndef __TIMER_H__ #define __TIMER_H__ namespace Util { // // 计时器回调函数. // typedef boost::function<void()> TimerCallback; typedef boost::function<void()> AsyncCallProc; /** * 设置并启动一个计时器. * * \param[in] timerID: 计时器 ID. * \param[in] nElapse: 计时器响应间隔. * \param[in] timerCbk: 计时器回调. * \param[in] bLoopTimer: 是否是循环计时器. * * \remarks * 当 bLoopTimer 为 false 时,计时器的回调函数只执行一次. * 然后该计时器会从计时器管理器中删除. */ void SetTimerCallback( void* timerID, int nElapse, TimerCallback timerCbk, bool bLoopTimer = true ); /** * 清除一个计时器. * * \param[in] timerID: 清除的计时器的 ID. * * \remarks * 如果计时器管理器中没有这个 ID 对应的计时器, * 将什么都不做.否则删除计时器. */ void KillTimerCallback(void* timerID); inline void AsyncCall(AsyncCallProc asyncProc) { int dummy = 0; SetTimerCallback(&dummy, 500, asyncProc, false); } #define ASYNC_CALL_BIND(class_name, method_name) \ boost::bind(&class_name::method_name, this) #define TIMER_CALL_BIND(class_name, method_name) \ ASYNC_CALL_BIND(class_name, method_name) } /*namespace Util ends here.*/ #endif /*__TIMER_H__*/
为什么我们要将 timerID 用 void* 表示呢?由于当定时器设置过多时,这个 ID 有可能重复,所以我们用一片内存的首地址来表示,尽量减小定时器的 ID 重复的可能性。我们还间接通过定时器实现了一个异步调用 AsyncCall 。
那么如何实现呢?首先,我们考虑到:
1、Timer 管理器要全局唯一,便于管理——用单件;
2、暴露的接口只有上述——实现全部放入 timer.cpp ;
3、计时器的 ID要尽量唯一,减小重复——考虑用回调类的this指针或者临时对象的地址;
4、任何类、线程均可使用该 Timer;
5、Timer 回调要简单易用——Boost::function 解决;
考虑到上述需求,我们在 timer.cpp 中的一个匿名命名空间(思考为什么不在Util 中)中定义一个单件类 CTimerMgr,并尽数实现 timer.h 中的类即可。走起:
#include "stdafx.h" #include "timer.h" #include "Singleton.h" using namespace Util; namespace { // // 计时器管理器类.模块外不可见. // class CTimerMgr : public Singleton<CTimerMgr> { public: struct TIME_ITEM { public: void* timerID; int nElapse; TimerCallback timerCbk; bool bLoop; }; typedef map<void*, TIME_ITEM> TimerMap; CTimerMgr() { _InitTimerMgr(); } ~CTimerMgr() { _UninitTimerMgr(); } void SetTimerCallback( void* timerID, int nElapse, TimerCallback timerCbk, bool bLoopTimer ) { TIME_ITEM newItem = { timerID, nElapse, timerCbk, bLoopTimer }; m_timers[timerID] = newItem; ::SetTimer(m_hTimerWnd, (UINT_PTR)timerID, nElapse, NULL); } void KillTimerCallback(void* timerID) { TimerMap::iterator iter = m_timers.find(timerID); if (iter != m_timers.end()) { ::KillTimer(m_hTimerWnd, (UINT_PTR)timerID); m_timers.erase(iter); } } private: void OnTimer(void* timerID) { TimerMap::iterator iter = m_timers.find(timerID); if (iter != m_timers.end()) { TIME_ITEM& item = iter->second; TimerCallback cbk = item.timerCbk; // 非循环Timer,删除之. if (!item.bLoop) { ::KillTimer(m_hTimerWnd, (UINT_PTR)timerID); m_timers.erase(iter); } cbk(); } } static HRESULT CALLBACK OnTimerWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch (uMsg) { case WM_TIMER: { void* timerID = reinterpret_cast<void*>(wParam); CTimerMgr::GetInstance().OnTimer(timerID); } break; default: break; } return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); } void _InitTimerMgr() { const WCHAR* const TIMER_CLS_NAME = L"__timer_cls_name"; const WCHAR* const TIMER_WND_NAME = L"__timer_wnd_name"; WNDCLASSEXW wndcls = {sizeof(wndcls)}; wndcls.hInstance = ::GetModuleHandle(NULL); wndcls.lpszClassName = TIMER_CLS_NAME; wndcls.hbrBackground = (HBRUSH)::GetStockObject(NULL_BRUSH); wndcls.lpfnWndProc = &CTimerMgr::OnTimerWndProc; wndcls.style = CS_HREDRAW | CS_VREDRAW; ::RegisterClassExW(&wndcls); m_hTimerWnd = ::CreateWindowExW( 0, TIMER_CLS_NAME, TIMER_WND_NAME, WS_OVERLAPPED, 0, 0, 0, 0, HWND_MESSAGE, NULL, wndcls.hInstance, NULL ); } void _UninitTimerMgr() { for (TimerMap::iterator iter = m_timers.begin(); iter != m_timers.end(); ++iter ) { ::KillTimer(m_hTimerWnd, (UINT_PTR)iter->first); } m_timers.clear(); } private: TimerMap m_timers; HWND m_hTimerWnd; }; } /*namespace anonymous ends here.*/ void Util::SetTimerCallback( void* timerID, int nElapse, TimerCallback timerCbk, bool bLoopTimer /* = true */ ) { CTimerMgr::GetInstance().SetTimerCallback( timerID, nElapse, timerCbk, bLoopTimer ); } void Util::KillTimerCallback(void* timerID) { CTimerMgr::GetInstance().KillTimerCallback(timerID); }
我们在 CTimerMgr 内部维护了一个定时器信息的 map。该 map 用定时器的 ID 做键值。在 CTimerMgr 内实现了一个隐藏在内部的Windows 窗口,所有的计时器消息将送往该窗口的 WM_TIMER 处理函数。看起来没有一点难度。但功能非常强大:
#include "timer.h" class CMyA { public: CMyA() : m_value(0) { } void f() { ::SetTimerCallback(this, 1000, TIMER_CALL_BIND(CMyA, _timer_proc)); } private: void _timer_proc() { cout<<m_value++<<endl; } int m_value; }; void _global_proc() { static int val = 0; cout<<val++<<endl; } int WinMain(HINSTANCE, HINSTANCE, LPCTSTR, int) { // ... int _dummy; ::SetTimerCallback(&_dummy, 1000, _global_proc); // ... return 0; }通过这个计时器,我们可以实现其他一些强大的功能,比如窗口动画、事件管理等等。下一章将讲解基于该 Timer 的窗口动画类的实现。