c++ 定时器

场景:C++工具种——定时器

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 的窗口动画类的实现。