简介
本文介绍chromium的base的timer的源码,地址在https://github.com/chromium/chromium/tree/master/base,我们讨论在base中是如何实现一个timer的。
windows下的timer
我们先来看看windows下如何实现一个简单的timer的
class A
{
public:
void static CALLBACK TimeProc(HWND hwnd, UINT message,
UINT idTimer, DWORD dwTime) {
std::cout << "timer times" << std::endl
}
};
int main()
{
SetTimer(NULL, 1, 1000, A::TimeProc);
MSG msg;
while(1) {
if (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
简单介绍下这个简单的timer,首先执行main函数时,即在主线程上用VC++的api设置一个timer,然后进入消息循环,等到GetMessage获取到消息后去分发执行,如果是WM_TIMER,便会执行那个回调函数TimeProc。
base的timer
base库立于跨平台,所以在实现timer的时候就没有使用vc++的api,同时vc++的api有一个弊端,就是回调函数须是一个C的函数,对于C++中类的成员函数不能使用。
1. 使用
源码位于timer.h|timer.cc中,在注释中可以找到timer的使用方式:
// Sample RepeatingTimer usage:
//
// class MyClass {
// public:
// void StartDoingStuff() {
// timer_.Start(FROM_HERE, TimeDelta::FromSeconds(1),
// this, &MyClass::DoStuff);
// }
// void StopDoingStuff() {
// timer_.Stop();
// }
// private:
// void DoStuff() {
// // This method is called every second to do stuff.
// ...
// }
// base::RepeatingTimer<MyClass> timer_;
// };
声明一个要重复执行的timer,在StartDoingStuff中调用Start开启定时器,第一个参数是执行的位置,第二个是时间间隔,然后是执行回调函数的对象和回调函数,之后每隔一段时间就会调用DoStuff,停止定时器就条用Stop函数。
2. 前置知识
base自己建立了一整套自己的体系,包括他自己MessageLoop,thread,TimeDelta等,我们下边也会提到。
3. 源码解读
首先列出.h的代码,我把注释删掉,这样显得简短:
#ifndef BASE_TIMER_H_
#define BASE_TIMER_H_
// IMPORTANT: If you change timer code, make sure that all tests (including
// disabled ones) from timer_unittests.cc pass locally. Some are disabled
// because they're flaky on the buildbot, but when you run them locally you
// should be able to tell the difference.
#include "base/base_export.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/time.h"
namespace base {
class BaseTimerTaskInternal;
class MessageLoop;
//-----------------------------------------------------------------------------
// This class wraps MessageLoop::PostDelayedTask to manage delayed and repeating
// tasks. It must be destructed on the same thread that starts tasks. There are
// DCHECKs in place to verify this.
//
class BASE_EXPORT Timer {
public:
// Construct a timer in repeating or one-shot mode. Start or SetTaskInfo must
// be called later to set task info. |retain_user_task| determines whether the
// user_task is retained or reset when it runs or stops.
Timer(bool retain_user_task, bool is_repeating);
// Construct a timer with retained task info.
Timer(const tracked_objects::Location& posted_from,
TimeDelta delay,
const base::Closure& user_task,
bool is_repeating);
virtual ~Timer();
// Returns true if the timer is running (i.e., not stopped).
bool IsRunning() const {
return is_running_;
}
// Returns the current delay for this timer.
TimeDelta GetCurrentDelay() const {
return delay_;
}
// Start the timer to run at the given |delay| from now. If the timer is
// already running, it will be replaced to call the given |user_task|.
void Start(const tracked_objects::Location& posted_from,
TimeDelta delay,
const base::Closure& user_task);
// Call this method to stop and cancel the timer. It is a no-op if the timer
// is not running.
void Stop();
// Call this method to reset the timer delay. The user_task_ must be set. If
// the timer is not running, this will start it by posting a task.
void Reset();
const base::Closure& user_task() const { return user_task_; }
const TimeTicks& desired_run_time() const { return desired_run_time_; }
protected:
// Used to initiate a new delayed task. This has the side-effect of disabling
// scheduled_task_ if it is non-null.
void SetTaskInfo(const tracked_objects::Location& posted_from,
TimeDelta delay,
const base::Closure& user_task);
private:
friend class BaseTimerTaskInternal;
// Allocates a new scheduled_task_ and posts it on the current MessageLoop
// with the given |delay|. scheduled_task_ must be NULL. scheduled_run_time_
// and desired_run_time_ are reset to Now() + delay.
void PostNewScheduledTask(TimeDelta delay);
// Disable scheduled_task_ and abandon it so that it no longer refers back to
// this object.
void AbandonScheduledTask();
// Called by BaseTimerTaskInternal when the MessageLoop runs it.
void RunScheduledTask();
// Stop running task (if any) and abandon scheduled task (if any).
void StopAndAbandon() {
Stop();
AbandonScheduledTask();
}
// When non-NULL, the scheduled_task_ is waiting in the MessageLoop to call
// RunScheduledTask() at scheduled_run_time_.
BaseTimerTaskInternal* scheduled_task_;
// Location in user code.
tracked_objects::Location posted_from_;
// Delay requested by user.
TimeDelta delay_;
// user_task_ is what the user wants to be run at desired_run_time_.
base::Closure user_task_;
// The estimated time that the MessageLoop will run the scheduled_task_ that
// will call RunScheduledTask().
TimeTicks scheduled_run_time_;
// The desired run time of user_task_. The user may update this at any time,
// even if their previous request has not run yet. If desired_run_time_ is
// greater than scheduled_run_time_, a continuation task will be posted to
// wait for the remaining time. This allows us to reuse the pending task so as
// not to flood the MessageLoop with orphaned tasks when the user code
// excessively Stops and Starts the timer.
TimeTicks desired_run_time_;
// Thread ID of current MessageLoop for verifying single-threaded usage.
int thread_id_;
// Repeating timers automatically post the task again before calling the task
// callback.
const bool is_repeating_;
// If true, hold on to the user_task_ closure object for reuse.
const bool retain_user_task_;
// If true, user_task_ is scheduled to run sometime in the future.
bool is_running_;
DISALLOW_COPY_AND_ASSIGN(Timer);
};
//-----------------------------------------------------------------------------
// This class is an implementation detail of OneShotTimer and RepeatingTimer.
// Please do not use this class directly.
template <class Receiver, bool kIsRepeating>
class BaseTimerMethodPointer : public Timer {
public:
typedef void (Receiver::*ReceiverMethod)();
// This is here to work around the fact that Timer::Start is "hidden" by the
// Start definition below, rather than being overloaded.
// TODO(tim): We should remove uses of BaseTimerMethodPointer::Start below
// and convert callers to use the base::Closure version in Timer::Start,
// see bug 148832.
using Timer::Start;
BaseTimerMethodPointer() : Timer(kIsRepeating, kIsRepeating) {}
// Start the timer to run at the given |delay| from now. If the timer is
// already running, it will be replaced to call a task formed from
// |reviewer->*method|.
void Start(const tracked_objects::Location& posted_from,
TimeDelta delay,
Receiver* receiver,
ReceiverMethod method) {
Timer::Start(posted_from, delay,
base::Bind(method, base::Unretained(receiver)));
}
};
//-----------------------------------------------------------------------------
// A simple, one-shot timer. See usage notes at the top of the file.
template <class Receiver>
class OneShotTimer : public BaseTimerMethodPointer<Receiver, false> {};
//-----------------------------------------------------------------------------
// A simple, repeating timer. See usage notes at the top of the file.
template <class Receiver>
class RepeatingTimer : public BaseTimerMethodPointer<Receiver, true> {};
//-----------------------------------------------------------------------------
// A Delay timer is like The Button from Lost. Once started, you have to keep
// calling Reset otherwise it will call the given method in the MessageLoop
// thread.
//
// Once created, it is inactive until Reset is called. Once |delay| seconds have
// passed since the last call to Reset, the callback is made. Once the callback
// has been made, it's inactive until Reset is called again.
//
// If destroyed, the timeout is canceled and will not occur even if already
// inflight.
template <class Receiver>
class DelayTimer : protected Timer {
public:
typedef void (Receiver::*ReceiverMethod)();
DelayTimer(const tracked_objects::Location& posted_from,
TimeDelta delay,
Receiver* receiver,
ReceiverMethod method)
: Timer(posted_from, delay,
base::Bind(method, base::Unretained(receiver)),
false) {}
void Reset() { Timer::Reset(); }
};
} // namespace base
#endif // BASE_TIMER_H_
- Timer是所有类的基类,也是在这个类里实现了所有的功能,一般我们不会直接用到
- BaseTimerMethodPointer这个类是OneShotTimer和RepeatingTimer实现的基础,主要是提供给他俩是否是重复性的和接受回调的类,使用是不会用到
- OneShotTimer这个类是只执行一次的回调函数的实现
- RepeatingTimer这个类就是重复性的执行回调函数的实现
- DelayTimer这个类类似于OneShotTimer,调用reset时,处于激活状态,回调函数调用后处于生效状态,直到下一次条用reset,另外是在声明这个类的对象时就需要指定回调函数,同时还不能中途更换回调函数
3.1 class Timer简介
然后我们主要class Timer的实现,这也是我们这篇的核心
timer有两个构造函数,第一个有两个参数,retain_user_task表示是否要保存要执行的回调函数,也就是这里的task以便之后可以重用,is_repeating这个定时器是不是重复性的。第二个构造函数,post_from表示的是记录使用,没有实际用处,就是记录函数执行的位置,可以说记录调用这个函数的位置,delay时间间隔,user_task是要执行的回调函数,is_repeating重复性的定时器。
然后下边也是定时器常用的api,开启(start),结束(stop)等。
简单介绍下成员:
- scheduled_task_是对user_task执行的包装,是BaseTimerTaskInternal类的对象,这个类是用来进行一些条件判断和最终把包装的函数抛到线程的事件循环中,然后由事件循环调用,然后调用回调函数。
- scheduled_run_time_这个是我们在开启定时器,指定了偏移时间后计算得到的task执行的时间
- desired_run_time_这个和schedule_run_time的含义也类似,一个例外是这个会在reset时重新设置task的执行时间,使用它和schedule_run_time可以来判断是不是要重用已经抛出去的task.
3.2 事件循环机制
我们来提一点关于chromium的时间循环机制。我们启动一个线程的时候,需要指定这个线程事件循环(messageloop),代码在messageloop.cc|.h中,然后这个线程就进入了事件循环,然后我们要是想要这个线程做一些事情,我们就需要向这个messageloop设置一些task,我们简单的来看一下这个messageloop使用的接口:
void PostTask(
const tracked_objects::Location& from_here,
const base::Closure& task);
bool TryPostTask(
const tracked_objects::Location& from_here,
const base::Closure& task);
void PostDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay);
void PostNonNestableTask(
const tracked_objects::Location& from_here,
const base::Closure& task);
void PostNonNestableDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay);
int SendTask(const tracked_objects::Location& from_here,
const base::Closure& task, int timeout = -1);
这里的task就是我们要做的事情,postDelayTask就是我们timer会用到的接口,表示一段延迟时间后去运行这个task。然后messageloop通过messagepump来进行具体的实现,messageloop有三种类型(default,ui,io),同时使用messagepump来实现跨平台。
3.3 timer的过程
我们来接下来详细的梳理下这个timer的过程,从start开始:
void Timer::Start(const tracked_objects::Location& posted_from,
TimeDelta delay,
const base::Closure& user_task) {
SetTaskInfo(posted_from, delay, user_task);
Reset();
}
start里调用两个函数,SetTaskInfo只是将task的相关消息获得。然后我们看Reset函数:
void Timer::Reset() {
DCHECK(!user_task_.is_null());
// If there's no pending task, start one up and return.
if (!scheduled_task_) {
PostNewScheduledTask(delay_);
return;
}
// Set the new desired_run_time_.
desired_run_time_ = TimeTicks::Now() + delay_;
// We can use the existing scheduled task if it arrives before the new
// desired_run_time_.
if (desired_run_time_ > scheduled_run_time_) {
is_running_ = true;
return;
}
// We can't reuse the scheduled_task_, so abandon it and post a new one.
AbandonScheduledTask();
PostNewScheduledTask(delay_);
}
首先判断是否已经有在运行的scheduled_task_(或者说已经抛出去但是还未运行),如果没有的话我们通过task来构造scheduled_task_然后抛到事件队列中运行,如果有在运行的scheduled_task_的话,我们要获得这个当前的task的运行的时间,如果这个时间大于正在运行的scheduled_task_的时间,表明我们其实可以重用这个正在运行的scheduled_task_,以此通过scheduled_task_来运行task,最后运行回调函数。如果正在运行的scheduled_task_的时间是大于当前的task需要运行的时间,需要抛弃掉在运行的scheduled_task_,然后构造新的scheduled_task_来运行。
然后我们看看关键的函数PostNewScheduledTask的实现:
void Timer::PostNewScheduledTask(TimeDelta delay) {
DCHECK(scheduled_task_ == NULL);
is_running_ = true;
scheduled_task_ = new BaseTimerTaskInternal(this);
ThreadTaskRunnerHandle::Get()->PostDelayedTask(posted_from_,
base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_)),
delay);
scheduled_run_time_ = desired_run_time_ = TimeTicks::Now() + delay;
// Remember the thread ID that posts the first task -- this will be verified
// later when the task is abandoned to detect misuse from multiple threads.
if (!thread_id_)
thread_id_ = static_cast<int>(PlatformThread::CurrentId());
}
首先设置标致为在运行,然后new一个BaseTimerTaskInternal,把他抛到这个线程的事件循环中等待运行,一段时间后会运行BaseTimerTaskInternal的run函数。计算时间和设置线程id。然后我们来看BaseTimerTaskInternal的Run函数和Abandon函数:
void Run() {
// timer_ is NULL if we were abandoned.
if (!timer_)
return;
// *this will be deleted by the task runner, so Timer needs to
// forget us:
timer_->scheduled_task_ = NULL;
// Although Timer should not call back into *this, let's clear
// the timer_ member first to be pedantic.
Timer* timer = timer_;
timer_ = NULL;
timer->RunScheduledTask();
}
// The task remains in the MessageLoop queue, but nothing will happen when it
// runs.
void Abandon() {
timer_ = NULL;
}
首先会判断timer_是不是为空,当这个scheduled_task_被执行Abandon后就为空,不为空表示并未被abandon,然后将这个scheduled_task_置空,这样去掉这个引用,在运行这个函数后自动释放这个对象,然后去运行真正的task:RunScheduledTask
void Timer::RunScheduledTask() {
// Task may have been disabled.
if (!is_running_)
return;
// First check if we need to delay the task because of a new target time.
if (desired_run_time_ > scheduled_run_time_) {
// TimeTicks::Now() can be expensive, so only call it if we know the user
// has changed the desired_run_time_.
TimeTicks now = TimeTicks::Now();
// Task runner may have called us late anyway, so only post a continuation
// task if the desired_run_time_ is in the future.
if (desired_run_time_ > now) {
// Post a new task to span the remaining time.
PostNewScheduledTask(desired_run_time_ - now);
return;
}
}
// Make a local copy of the task to run. The Stop method will reset the
// user_task_ member if retain_user_task_ is false.
base::Closure task = user_task_;
if (is_repeating_)
PostNewScheduledTask(delay_);
else
Stop();
task.Run();
// No more member accesses here: *this could be deleted at this point.
}
然后来看这个函数,之前我们有说可能会重用schedule_task_,但是没有说怎么重用,这里是这样的,我们会检查这个运行的schedule_task_是不是正好是我们希望的时间,如果不是的话,我们需要计算下偏移然后会重新抛回到事件循环中,如果不是重用的话,且运行的时间正好是我们希望的时间,就去运行我们的task:task.Run();
同时如果是要重复性的,我们还是要继续创建新的schedule_task_来运行,否则stop这个定时器。
3.4 注意
到这里我们就讲完了整个的流程,我们需要注意的一点是,为什么他要借助scheduled_task_来运行我们的task呢,如果我们只用task,我们就没办法来实现重用的这个概念,因为可能事件队列里正在运行的task和我即将要运行的task是不一样的,如果有了scheduled_task_作为一层包装后,重用时task已经被重置了,所以scheduled_task_可以放心的调用task了,而不用担心本次运行的task和上次运行的task不一致的现象。
3.5 代码实现
如果实在主线程中运行的话可以参照timer.h里的注释,如果需要创建一个线程,同时需要在这个线程里运行这个timer的话可以这样跑起来:
class MyClass
{
public:
void StartDoingStuff() {
timer_.Start(FROM_HERE, TimeDelta::FromSeconds(1),
this, &MyClass::DoStuff);
}
void StopDoingStuff() {
timer_.Stop();
}
private:
void DoStuff() {
// This method is called every second to do stuff.
...
}
base::RepeatingTimer<MyClass> timer_;
};
MyClass myClass;
base::Thread thread_;
thread_.Start();
thread_.message_loop()->PostTask(FROM_HERE,
base::Bind(&MyClass::StartDoingStuff, base::Unretained(myClass)));