防止boost::asio::deadline_timer在修改系统时间后失效

boost::asio::deadline_timer使用的计量时间是系统时间,因此修改系统时间会影响deadline_timer的行为。例如,调用了expires_from_now设置1分钟超时后,立刻把系统时间改成一天前,那么要过一天时间才会超时。这个特性可能会影响程序功能的正常使用,因此我们通常想要的是一个不会受系统时间影响的定时器。

事实上,boost::asio::steady_timer就是一个这样的定时器,它基于std::chrono::steady_clock实现。std::chrono::steady_clock是一个稳定的时钟,不随系统时间变化而变化。既然如此,直接用steady_timer代替deadline_timer不就可以了吗?理论上来说是可以的,但实际上,在Visual C++ 2013环境下,这是行不通的,因为Visual C++ 2013标准库中的std::chronno::steady_clock并不符合标准,它仍然会受系统时间影响!

有三种方法可以解决这个问题。第一是升级到Visual C++ 2015,这个版本的std::chronno::steady_clock总算符合标准了;第二是修改boost的编译选项,定义BOOST_ASIO_DISABLE_STD_CHRONO宏,这样可以禁止boost使用std::chrono,转而使用boost::chrono;第三是本文要介绍的方法,即定制deadline_timer,让它变成稳定的定时器。

deadline_timer实际上是basic_deadline_timer的特化版本,它的定义如下:

1
typedef basic_deadline_timer<boost::posix_time::ptime> deadline_timer;

basic_deadline_timer是一个模板类,它的定义如下:

1
2
3
4
5
template<
typename Time,
typename TimeTraits = boost::asio::time_traits<Time>,
typename TimerService = deadline_timer_service<Time, TimeTraits>>
class basic_deadline_timer : public basic_io_object< TimerService >

从以上定义的模板参数可以看出,basic_deadline_timer提供了灵活的可定制性。这里我们关注的重点是前面两个模板参数,其中第一个参数Time指定时间值的类型,第二个参数TimeTraits指定时间值的特性类,特性类用来对时间值进行各种操作。TimeTraits使用boost::asio::time_traits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/// Time traits specialised for posix_time.
template <>
struct time_traits<boost::posix_time::ptime>
{
/// The time type.
typedef boost::posix_time::ptime time_type;

/// The duration type.
typedef boost::posix_time::time_duration duration_type;

/// Get the current time.
static time_type now()
{
#if defined(BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK)
return boost::posix_time::microsec_clock::universal_time();
#else // defined(BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK)
return boost::posix_time::second_clock::universal_time();
#endif // defined(BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK)
}

/// Add a duration to a time.
static time_type add(const time_type& t, const duration_type& d)
{
return t + d;
}

/// Subtract one time from another.
static duration_type subtract(const time_type& t1, const time_type& t2)
{
return t1 - t2;
}

/// Test whether one time is less than another.
static bool less_than(const time_type& t1, const time_type& t2)
{
return t1 < t2;
}

/// Convert to POSIX duration type.
static boost::posix_time::time_duration to_posix_duration(
const duration_type& d)
{
return d;
}
};

可以看到,TimeTraits需要提供time_typeduration_type两种类型来分别表示一个时间点和一段时间;需要提供now方法来获取当前时间;需要提供addsubtractless_than方法来计算和比较时间;最后还需要to_posix_duration方法把duration_type类型转换成boost::posix_time::time_duration类型。

显然,对于定制的basic_deadline_timer,时间值类型Time可以是任意类型,并且它的含义并没有硬性规定,例如,它可以是以毫秒或纳秒为单位的时间,也可以是CPU时钟的周期数,只要提供了正确的TimeTraits特性类把这个定制的时间值转换成boost认识的时间值即可。

接下来要选择一种与系统时间无关的时间值类型来定制basic_deadline_timer。在Windows平台下,很容易想到可以使用QueryPerformanceCounter和QueryPerformanceFrequency,这两个API提供了高精度的时间度量,与系统时间无关。QueryPerformanceCounter用来查询当前CPU时钟的周期数,是64位整数,这个是理想的时间值类型。要把CPU时钟周期数转换成具体的时间还需要调用QueryPerformanceFrequency查询CPU时钟的频率,即1秒内的CPU时钟周期数,然后通过简单的计算即可得到。

下面的是使用QueryPerformanceCounter和QueryPerformanceFrequency定制的TimeTraits:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class TimeTraits {
public:
typedef std::int64_t time_type;

class duration_type {
public:
duration_type() : value(0) { }
duration_type(std::int64_t value) : value(value) { }

std::int64_t value;
};

public:
static void Initialize() {

LARGE_INTEGER frequence_large_integer = { 0 };
QueryPerformanceFrequency(&frequence_large_integer);
frequence = frequence_large_integer.QuadPart;
}

static duration_type GetMinutes(std::int64_t minutes) {
return duration_type(minutes * 60 * frequence);
}

static duration_type GetSeconds(std::int64_t seconds) {
return duration_type(seconds * frequence);
}

static duration_type GetMilliseconds(std::int64_t milliseconds) {
return duration_type(milliseconds * (frequence / 1000));
}

static time_type now() {

LARGE_INTEGER counter = { 0 };
QueryPerformanceCounter(&counter);
return counter.QuadPart;
}

static time_type add(time_type time, duration_type duration) {
return time + duration.value;
}

static duration_type subtract(time_type time1, time_type time2) {
return duration_type(time1 - time2);
}

static bool less_than(time_type time1, time_type time2) {
return time1 < time2;
}

static boost::posix_time::time_duration to_posix_duration(duration_type duration) {

std::int64_t microseconds = (duration.value * 1000 * 1000) / frequence;
return boost::posix_time::microseconds(microseconds);
}

private:
TimeTraits();

private:
static std::int64_t frequence;
};

CPU时钟的频率是固定的,只需要调用QueryPerformanceFrequency查询一次即可,因此这里增加了Initialize方法来初始化CPU时钟频率,要在程序启动后的某个时机来调用该方法进行初始化。要注意的是duration_type不能跟time_type相同,这是TimeTraits的硬性规定,boost内部的代码依赖了这个规定,违反它会导致编译失败。所以要针对duration_type额外定义一个看似冗余的类型。

另一个值得注意的地方是,上面的定义增加了GetMinutesGetSecondsGetMilliseconds方法,这几个方法用来将具体的时间转换成我们定制的时间值,即CPU时钟周期数。这是因为在调用定时器的expires_from_now等方法设置超时值的时候,必须使用TimeTraits的duration_type,提供这几个方法可以方便使用。

最后,将这个定制的TimeTraits作为basic_deadline_timer的模板参数即可:

1
typedef boost::asio::basic_deadline_timer<std::int64_t, TimeTraits> Timer;

使用方法基本上与dealine_timer一样,如下所示:

1
2
3
4
5
6
7
8
9
10
//在某个时机初始化TimeTraits
TimeTraits::Initialize();

//在某个地方定义io_service
boost::asio::io_service io_service;

//使用定制的Timer
Timer timer(io_service);
timer.expires_from_now(TimeTraits::GetSecnods(10));
timer.wait();