《ZLToolKit源码学习笔记》(14)事件轮询模块之定时器

 系列文章目录

《ZLToolKit源码学习笔记》(1)VS2019源码编译

《ZLToolKit源码学习笔记》(2)工具模块之日志功能分析

《ZLToolKit源码学习笔记》(3)工具模块之终端命令解析

《ZLToolKit源码学习笔记》(4)工具模块之消息广播器

《ZLToolKit源码学习笔记》(5)工具模块之资源池

《ZLToolKit源码学习笔记》(6)线程模块之整体框架概述

《ZLToolKit源码学习笔记》(7)线程模块之线程池组件:任务队列与线程组

《ZLToolKit源码学习笔记》(8)线程模块之线程负载计算器

《ZLToolKit源码学习笔记》(9)线程模块之任务执行器

《ZLToolKit源码学习笔记》(10)线程模块之线程池

《ZLToolKit源码学习笔记》(11)线程模块之工作线程池WorkThreadPool

《ZLToolKit源码学习笔记》(12)事件轮询模块之整体框架概述

《ZLToolKit源码学习笔记》(13)事件轮询模块之管道的简单封装

《ZLToolKit源码学习笔记》(14)事件轮询模块之定时器(本文)

《ZLToolKit源码学习笔记》(15)事件轮询模块之事件轮询器EventPoller

《ZLToolKit源码学习笔记》(16)网络模块之整体框架概述

《ZLToolKit源码学习笔记》(17)网络模块之基础接口封装类SockUtil

《ZLToolKit源码学习笔记》(18)网络模块之Buffer缓存

《ZLToolKit源码学习笔记》(19)网络模块之套接字封装

《ZLToolKit源码学习笔记》(20)网络模块之TcpServer

《ZLToolKit源码学习笔记》(21)网络模块之TcpClient与Session

《ZLToolKit源码学习笔记》(22)网络模块之UdpServer


前言

任务的定时执行在服务器编程中经常用到,本节学习下ZLToolKit封装的定时器。Timer.h和Timer.cpp。


目录

 系列文章目录

前言

一、概述

二、TaskCancelableImp

三、Timer

3.1、构造函数

3.2、析构函数

四、EventPoller

4.1、doDelayTask-添加延时任务 

4.2、flushDelayTask-刷新延时任务

4.3、getMinDelay-获取最近待执行任务的间隔时间

五、测试


一、概述

 ZLToolKit的定时器是通过线程+循环判断任务的执行时间是否到达来实现,添加任务时,记录该任务下一次执行时间点,然后把定时任务插入到队列中,在线程runLoop中,检测队列中是否有任务已经到达执行时间点,有则立即执行该任务,否则,把最近将要执行的任务的时间点作为线程的休眠时间。以此循环,线程下一次唤醒后重复执行上述操作。

以上,定时器功能主要涉及到三个类。

TaskCancelableImp,对任务的封装,支持任务的取消;

EventPoller,事件轮询器,管理定时任务,在线程中循环判任务是否到达执行时间,并执行已经到达时间的任务。对于需要重复定时执行的任务,在本次执行完成后,会重新计算任务的执行时间点,并添加到延时任务队列中。 

Timer,定时器封装类,将任务(TaskCancelableImp)和事件轮询器(EventPoller)关联起来。Timer对象析构时,会取消任务。


二、TaskCancelableImp

该类在线程池一节中已经学习过,这里不做分析。

《ZLToolKit源码学习笔记》(10)线程模块之线程池_秦时小-CSDN博客


三、Timer

该类只有构造函数和析构函数。在构造函数中,将定时任务添加到事件轮询器中进行管理,在析构函数中,取消任务。

3.1、构造函数

Timer::Timer(float second,
             const function<bool()> &cb,
             const EventPoller::Ptr &poller,
             bool continueWhenException) {
    _poller = poller;
    if(!_poller){
        _poller = EventPollerPool::Instance().getPoller();
    }
    _tag = _poller->doDelayTask((uint64_t)(second * 1000), [cb, second , continueWhenException]() {
        try {
            if (cb()) {
                //重复的任务
                return (uint64_t) (1000 * second);
            }
            //该任务不再重复
            return (uint64_t) 0;
        }catch (std::exception &ex){
            ErrorL << "执行定时器任务捕获到异常:" << ex.what();
            return continueWhenException ? (uint64_t) (1000 * second) : 0;
        }
    });
}

可以看到,second是任务的执行间隔,cb是任务回调,poller是事件轮询器,continueWhenException确定在发生异常时定时任务是否还继续执行。

通过调用eventPoller的doDelayTask接口将任务添加到事件轮询器中进行管理,cb的返回值决定定时任务是否继续下一轮执行,true则继续,false则取消。

3.2、析构函数

析构函数中,取消了任务。

Timer::~Timer() {
    auto tag = _tag.lock();
    if(tag){
        tag->cancel();
    }
}

四、EventPoller

 该类主要是对事件的管理。支持的事件类型较多,这里仅关注与定时器相关的。

定时任务统一添加在multimap中管理。key是任务的下一次执行时间点,value是任务。自动根据任务执行时间点进行升序排列,遍历时,第一条即就是会最先到达执行时间的任务。

multimap<uint64_t, DelayTask::Ptr> _delay_task_map;

这里看一下任务的类型DelayTask,可以看到类型实际上就是TaskCancelableImp的一个特例化。

using DelayTask = TaskCancelableImp<uint64_t(void)>;

这部分单功能测试可以参见ZLToolKit\tests\test_delayTask.cpp文件。 

4.1、doDelayTask-添加延时任务 

DelayTask::Ptr EventPoller::doDelayTask(uint64_t delayMS, function<uint64_t()> task) {
    DelayTask::Ptr ret = std::make_shared<DelayTask>(std::move(task));
    auto time_line = getCurrentMillisecond() + delayMS;
    async_first([time_line, ret, this]() {
        //异步执行的目的是刷新select或epoll的休眠时间
        _delay_task_map.emplace(time_line, ret);
    });
    return ret;
}

任务的执行时间 = 当前时间 + 定时间隔。使用async_first来异步的执行任务的添加操作,通过内置的管道事件,先唤醒休眠中的runLoop线程,将任务添加到_delay_task_map中。

4.2、flushDelayTask-刷新延时任务

uint64_t EventPoller::flushDelayTask(uint64_t now_time) {
    decltype(_delay_task_map) task_copy;
    task_copy.swap(_delay_task_map);

    for (auto it = task_copy.begin(); it != task_copy.end() && it->first <= now_time; it = task_copy.erase(it)) {
        //已到期的任务
        try {
            auto next_delay = (*(it->second))();
            if (next_delay) {
                //可重复任务,更新时间截止线
                _delay_task_map.emplace(next_delay + now_time, std::move(it->second));
            }
        } catch (std::exception &ex) {
            ErrorL << "EventPoller执行延时任务捕获到异常:" << ex.what();
        }
    }

    task_copy.insert(_delay_task_map.begin(), _delay_task_map.end());
    task_copy.swap(_delay_task_map);

    auto it = _delay_task_map.begin();
    if (it == _delay_task_map.end()) {
        //没有剩余的定时器了
        return 0;
    }
    //最近一个定时器的执行延时
    return it->first - now_time;
}

 执行已经到期的任务,并返回最近一个定时器的执行延时。

4.3、getMinDelay-获取最近待执行任务的间隔时间

uint64_t EventPoller::getMinDelay() {
    auto it = _delay_task_map.begin();
    if (it == _delay_task_map.end()) {
        //没有剩余的定时器了
        return 0;
    }
    auto now = getCurrentMillisecond();
    if (it->first > now) {
        //所有任务尚未到期
        return it->first - now;
    }
    //执行已到期的任务并刷新休眠延时
    return flushDelayTask(now);
}

 获取最近将要执行的任务距离当前时间的间隔。runLoop线程再次休眠前,调用getMinDelay函数更新自己的休眠时间为距离下一个定时任务的执行间隔时间。比如,当前时间08:29:58,最近的任务在08:30:00时会执行,那么休眠时间就是2秒。

flushDelayTask中,会执行已经到达执行时间的任务。


五、测试

 测试程序见ZLToolKit\tests\test_timer.cpp,演示了可重复执行、不可重复执行、以及发生异常的定时任务的使用。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秦时小

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值