zoukankan      html  css  js  c++  java
  • Qt 定时调度

    需求:后台常驻进程中实现定时调度(计划任务,每隔若干分钟执行任务)。

    可模拟linux下cron的功能。

    定义配置文件格式如下:

    #[id] [start_time: yyyy-MM-dd-hh:mm] [period: min] [program] [params...]
    #1 2014-12-01-00:00 24 notepad a.txt
    task1 2015-09-10-12:00 1440 python %ABER_HOME%work.py -t 1 -s 2
    taskB 2014-01-01-00:05 60 calc

    其中"#"表示注释。

    格式说明:

    • id. 任务ID,保证各ID不重复即可。
    • start_time. 任务第一次开始时间,格式为yyyy-MM-dd-hh:mm。
    • period. 任务重复执行间隔,单位min。
    • program. 程序名称。
    • params. 程序参数,数量不定。

    任务结构体设计:

    typedef struct _TIME_ENTRY
    {
        QString taskId;               //任务ID
        int period;                      //任务执行周期
        QDateTime start_time;    //第一次启动时间
        QDateTime next_time;    //预计下一次启动任务的时间
        QString program;        //程序名称
        QStringList params;        //参数列表
    } TIME_ENTRY;    

    对任务调度器包装线程类:

    class CTimerManager;
    class CTimerThread : public QThread
    {
    public:
        CTimerThread(CTimerManager &timerManager);
        void run();
    
    private:
        bool m_run_tag;
        CTimerManager *mp_timer_manager;
    };

    任务调度器:

    class CTimerManager
    {
        friend class CTimerThread;
    public:
        CTimerManager();
        ~CTimerManager();
    
        void Init();
        void Run();
    
    public:
        static int CompareTimeEntry(const TIME_ENTRY &entry1, const TIME_ENTRY &entry2);   //比较TimeEntry
        static QDateTime CalcNextTime(QDateTime start_time, int period, bool allowEqual);  //计算下次运行时间
    
    private:
        int ReloadTimeConfig();    //重新初始化:1.重载配置文件;更新任务Map
        int ReadTimeConfig(QMap<QString, TIME_ENTRY> &taskMap);    //解析配置文件,存在临时Map里
        void UpdateTimerMap(QMap<QString, TIME_ENTRY> &taskMap);    //将临时Map合并到成员的Map里。
        void CheckTaskMap();      //检查Map各任务是否到时间
    
        int StartProgram(TIME_ENTRY &entry);    //启动任务
    
    private:
        QMap<QString, TIME_ENTRY> m_taskMap;
        QMap<QString, TIME_ENTRY> m_newTaskMap;
        CTimerThread *m_thd;     //与线程对象紧密协作
    };

    线程启动代码:

    CTimerThread::CTimerThread(CTimerManager &timerManager)
    {
        this->m_run_tag = false;
        this->mp_timer_manager = &timerManager;
    }
    
    void CTimerThread::run()
    {
        int cnt = 0;
        while(true)
        {
            if(--cnt <= 0)
            {
                cnt = RELOAD_CONFIG_CNT;
                this->mp_timer_manager->ReloadTimeConfig();
            }
            this->mp_timer_manager->CheckTaskMap();
            asleep(CHECK_INTERVAL * 1000);
        }
    }

     调度器核心代码:

    CTimerManager::CTimerManager() : m_thd(NULL)
    {
    }
    
    CTimerManager::~CTimerManager()
    {
        if(m_thd != NULL)
        {
            delete m_thd;
        }
    }
    
    int CTimerManager::ReloadTimeConfig()
    {    
        qDebug() << "RELOAD";
        this->ReadTimeConfig(m_newTaskMap);
        this->UpdateTimerMap(m_newTaskMap);    
    
        return 0;
    }
    
    /**
     * 比较两个TIME_ENTRY
     * 忽略比较NextDateTime;
     * return 0:相同;1:不相同
     **/
    int CTimerManager::CompareTimeEntry(const TIME_ENTRY &entry1, const TIME_ENTRY &entry2)
    {
        if(entry1.period != entry2.period)
        {
            return 1;
        }
    
        if(entry1.start_time != entry2.start_time)
        {
            return 2;
        }
    
        if(entry1.program != entry2.program)
        {
            return 3;
        }
    
        if(entry1.params.size() != entry2.params.size())
        {
            return 4;
        }
        else
        {
            for(int i=0; i<entry1.params.size(); i++)
            {
                if(entry1.params.at(i).compare(entry2.params.at(i)) != 0)
                {
                    return 5;
                }
            }
        }
    
        return 0;
    }
    
    void CTimerManager::Init()
    {
        //ReloadTimeConfig();
    }
    
    /**
     * 计算下次启动时间
     * allowEqual: 是否允许下次时间与当前时间相同。
     * 读配置文件时:允许;执行任务后,计算下一次执行时间:不允许
     **/
    QDateTime CTimerManager::CalcNextTime(QDateTime start_time, int period, bool allowEqual)
    {
        QDateTime cur_time = QDateTime::currentDateTime();
        cur_time.setTime(QTime(cur_time.time().hour(), cur_time.time().minute(), 0));
        QDateTime next_time;
        next_time = start_time;
    
        if(next_time > cur_time)
        {
            //下次时间晚于当前时间
            return next_time;
        }
        
        while(next_time < cur_time)
        {
            int secsTo = next_time.secsTo(cur_time);
            int cnt = secsTo / PERIOD_UNIT / period;
            int mod = secsTo  % (period * PERIOD_UNIT);
            if(mod != 0)
            {
                cnt += 1;
            }
            next_time = next_time.addSecs(period * PERIOD_UNIT * cnt);
        }
        if(next_time == cur_time)
        {
            if(!allowEqual)
            {
                next_time = next_time.addSecs(period * PERIOD_UNIT);
            }
        }
    
        return next_time;
    }
    
    /** 
     * 读取定时任务配置文件
     * 格式如下:
     * #[name] [start from: hour:min] [every: min] [program] [parameters]
     * #max period: 2147483min = 1491.308day
     * e.g.
     * n++ 12:40 60 notepad++ d:envisionlogupgrade_svr.log
     * calculator 00:00 1 calc 
     *
     **/
    int CTimerManager::ReadTimeConfig(QMap<QString, TIME_ENTRY> &taskMap)
    {
        taskMap.clear();    //Fix bug #23834 2014-12-19
        QString config_filename = g_prjhome.c_str();
        config_filename += TIME_CONFIG_FILENAME;
    
        QFile qfile(config_filename);
    
        if(qfile.exists()==false)
        {
            config_filename = g_prjhome.c_str();
            config_filename += TIME_CONFIG_FILENAME_BAK;
            qfile.setFileName(config_filename);
    
            if(qfile.exists()==false)
            {
                return -1;
            }
        }
    
        if (qfile.open(QIODevice::ReadOnly | QIODevice::Text) == false)
        {
            return -1;
        }
    
        QTextStream in(&qfile);
        while (!in.atEnd()) 
        {
            QString line = in.readLine();
            line = line.simplified();
            line = line.trimmed();
            if (line.isEmpty() || line.startsWith("#"))
            {
                continue;
            }
        
            QStringList list = line.split(" ", QString::SkipEmptyParts);
            if(list.size() < 4)
            {
                continue;
            }
    
            TIME_ENTRY entry;
            entry.name = list.at(0);
            QDateTime aTime = QDateTime::fromString(list.at(1), "yyyy-MM-dd-hh:mm");
            if(!aTime.isValid() || aTime < QDateTime::fromString("2000-01-01", "yyyy-MM-dd"))
            {
                qDebug() << QString("Time. not valid") << list.at(1);
                continue;
            }
            entry.start_time.setDate(aTime.date());
            entry.start_time.setTime(QTime(aTime.time().hour(), aTime.time().minute(), 0));
            
    
            bool isOk = false;
            int period = list.at(2).toInt(&isOk);
            if(!isOk)
            {
                qDebug() << QString("int not valid. ") << list.at(2);
                continue;
            }
            if(period > MAX_PERIOD_MINUTE)
            {
                period = MAX_PERIOD_MINUTE;
            }
            else if(period <=0)
            {
                period = 1;
            }
            entry.period = period;
    
            entry.next_time = CalcNextTime(entry.start_time, entry.period, true);
    
            entry.program = list.at(3);
    
            for(int i=0; i<4; i++)
            {
                list.removeFirst();
            }
    
            entry.params = list;
    
            taskMap[entry.name] = entry;
            qDebug() << entry.name << entry.program << entry.next_time;
        }
    
        qfile.close();
    
        return 0;
    }
    
    /**
     * 开线程
     **/
    void CTimerManager::Run()
    {
         if(this->m_thd != NULL)
         {
             return;
         }
    
         this->m_thd = new CTimerThread(*this);
         m_thd->start();
    }
    
    void CTimerManager::CheckTaskMap()
    {
        QString name;
        QDateTime cur_time;
        cur_time = QDateTime::currentDateTime();
        cur_time.setTime(QTime(cur_time.time().hour(), cur_time.time().minute(), 0));
    
        foreach(name, this->m_taskMap.keys())
        {
            if(this->m_taskMap.contains(name) == NULL)
            {
                qDebug() << "ERROR: cannot find " << name;
            }
    
            TIME_ENTRY &entry = this->m_taskMap[name];
            if(cur_time >= entry.next_time)
            {
                entry.next_time = CalcNextTime(entry.next_time, entry.period, false);
                StartProgram(entry);
            }
        }
    }
    
    int CTimerManager::StartProgram(TIME_ENTRY &entry)
    {
        QDateTime cur_time = QDateTime::currentDateTime();
        
        bool ret = QProcess::startDetached(entry.program, entry.params);
    
        if(ret == false)
        {
            qDebug() << "Cannot start. " << entry.name;
            return -1;
        }
    
        qDebug() << "Start. " << entry.name << entry.program << entry.next_time;
        return 0;
    }
    
    void CTimerManager::UpdateTimerMap(QMap<QString, TIME_ENTRY> &taskMap)
    {
        QString name;
    
        //在列表移除中新列表中不存在的任务
        QMapIterator<QString, TIME_ENTRY> iterOld(m_taskMap);
        while(iterOld.hasNext())
        {
            iterOld.next();
            name = iterOld.key();
    
            if(!taskMap.contains(name))
            {
                this->m_taskMap.remove(name);
                qDebug()<<"remove "<<name << iterOld.value().program;
            }
        }
    
    
        //比对新列表与老列表,新增任务或修改任务
        QMapIterator<QString, TIME_ENTRY> iterNew(taskMap);
        while(iterNew.hasNext())
        {
            iterNew.next();
            name = iterNew.key();
    
            if(!this->m_taskMap.contains(name))
            {
                //New task
                this->m_taskMap[name] = iterNew.value();        
                qDebug()<<"add "<<name << iterNew.value().program<< iterNew.value().next_time;
            }
            else
            {
                if(CompareTimeEntry(iterNew.value(), this->m_taskMap[name]) != 0)
                {
                    this->m_taskMap[name] = iterNew.value();        
                    qDebug()<<"modify "<<name << iterNew.value().program << iterNew.value().next_time;
                }
            }
        }
    }
    View Code

    CTimerManager与CTimeThread使用了交叉引用,CTimeManager::run()启动CTimerThread,CTimerThread启动while死循环,引用CTimerManager对象的reload(), check()等操作。

    最终只需把CTimerManager暴露给外部,隐藏了CTimerThread的细节,解耦:

    CTimerManager ctm;
    ctm.Init();
    ctm.Run();

    CalcNextTime()函数是以start_time为起点,累加period,直到时间不早于当前时刻,每次必须和实时系统时间比较,保证正确性(不能简单存时刻,每次触发后增加period,这样万一有一次没触发,以后都将不触发;比较实时系统时间比较保险,有利于程序今后的扩展)。

    函数有allowEqual参数,该参数为true适用于重载文件,为false适用于轮询过程。

    /**
     * 计算下次启动时间
     * allowEqual: 是否允许下次时间与当前时间相同。
     * 读配置文件时:允许;执行任务后,计算下一次执行时间:不允许
     **/
    QDateTime CTimerManager::CalcNextTime(QDateTime start_time, int period, bool allowEqual)
    {
        QDateTime cur_time = QDateTime::currentDateTime();
        cur_time.setTime(QTime(cur_time.time().hour(), cur_time.time().minute(), 0));
        QDateTime next_time;
        next_time = start_time;
    
        if(next_time > cur_time)
        {
            //下次时间晚于当前时间
            return next_time;
        }
        
        while(next_time < cur_time)
        {
            int secsTo = next_time.secsTo(cur_time);
            int cnt = secsTo / PERIOD_UNIT / period;
            int mod = secsTo  % (period * PERIOD_UNIT);
            if(mod != 0)
            {
                cnt += 1;
            }
            next_time = next_time.addSecs(period * PERIOD_UNIT * cnt);
        }
        if(next_time == cur_time)
        {
            if(!allowEqual)
            {
                next_time = next_time.addSecs(period * PERIOD_UNIT);
            }
        }
    
        return next_time;
    }
    View Code

    重载文件。因重载的时刻可能恰好是可以触发任务的时刻,所以allowEqual=true:

    entry.next_time = CalcNextTime(entry.start_time, entry.period, true);
    //...
    taskMap[entry.name] = entry;

    轮询。在当前时刻已经可以触发的情况下,设allowEqual=false,得到下一个时刻:

    TIME_ENTRY &entry = this->m_taskMap[name];
    if(cur_time >= entry.next_time)
    {
        entry.next_time = CalcNextTime(entry.next_time, entry.period, false);
        StartProgram(entry);
    }

    P.S.

    开发过程是先开发的轮询,再加入重载。

    轮询主体流程较为简单,但加入重载机制后,CalcNextTime()需要重新设计,于是才引入了bool allowEqual参数。

    自测较为繁琐,好多时间是在傻等。

  • 相关阅读:
    ASP.Net Core MVC+Ajax 跨域
    ASP.Net Core MVC 发生二次请求
    Spire高效稳定的.NET组件
    ASP.Net Core Razor+AdminLTE 小试牛刀
    二维码神器QRCoder
    读入 并查集 gcd/exgcd 高精度 快速幂
    Codeforces 348 D
    Gym
    BZOJ 3894 文理分科 最小割
    BZOJ 2132 圈地计划 最小割
  • 原文地址:https://www.cnblogs.com/daxia319/p/4797463.html
Copyright © 2011-2022 走看看