概述

最近接到一个任务 要做一个《计划任务》的东西。简而言之的说 就是事先设定好时间 定期执行指定代码的功能 我们这个很简单 就是每天或者每几天 那天的一个固定时间比如23:20执行一段固定代码,好,看一个界面

实现一个“计划任务”机制-LMLPHP

是不是很熟悉 哇哈哈哈,类似 Windows自带的 计划任务功能。 对就是这个。按说的话微软自家的东西 肯定有对应的接口或者 方便衔接的东西,于是网上找来找去 ,最终的结果还是没有采用他,一个原因是Windows自带的计划任务功能太复杂 我根本用不了那么多,第二个原因是没有找到一个让我舒心的调用方式。期间也找过其它的第三方的东西比如quartz 是很牛逼 看了下也是扯淡 大部头的东西 又是要安装这安装那的 不适合我的简易需求环境。

实现原理

后来静下心来仔细想了下 凭什么到指定时间执行指定代码 ,还不是程序开始的时候根据计划任务的时间进行了一个计算 然后设置一个timer让代码到期执行吗,难道还有什么其他歪可以找吗?搞清楚了事情的本质接下来就好办了,就像期间找过一个mysql不能登录的问题网上只说啥啥啥方式在命令行输入 仔细细看一下那个图上的语句明显是一个mysql的控制台并不一定要按照他说的流程打开 有可能我没添加环境变量各种原因打不开 我直接运行mysql控制台就好了, 所有做事情做不通前要想一下为很么 会好办的多。计划任务本来不就是算过期时间吗?什么你怀疑timer不稳定?不用timer用什么?按说时间计算是操作系统自暴露的一个API最基本的一个功能,就像文件读取 就那样了对于我们搞应用开发的 已经没有更底层的可以挖了。就算你拿C++来 我相信还是只有用类似timer的这种玩意儿 不相信还有其他歪可以找。timer不稳定 不要使用winform界面的那个timer 那玩意儿估计确实不稳定 命名空间下有好几个同名的东西。其实过程无法就是程序启动的时候计算过期时间 按从设定之处开始算 接下来什么时候执行 ,一个timer 加计划任务数据 经过精密的逻辑编程即可解决所有问题 不用去搞任何第三方的东西 ,并且我还有可控的 衔接友好的 界面 进行 任务管理。

实现

其中最主要的是构建一个ScheduleItemObj对象,里面有任务开始时间 间隔天数 任务的当天执行时间 ,相信聪明如你 ,最重要的就是 里面有一个timer 然后 一个委托 委托里面是你要执行的代码,然后就是初始化方法 初始化的时候进行逻辑计算 确定下次执行时间 设置timer ,当timer往后每次执行的时候自动计算下次时间 然后设置timer ,如此往复即可。多个任务放一个list里 通过propertyChange 进行界面更新,与管理。整个程序的结构大概就是这样了。我们使用的是System.Timers.Timer

以下是实现代码:注意最重要的一段代码是计算下次任务执行时间逻辑的处理,执行时间的机制:从创建的当天 的指定时间 ,每过指定单位量时间执行计划任务,比如每天10:10:10 执行,如果创建的时候是09:00:00 则当天的 10:10:10 首次执行 往后以此类推。如果创建的时候是11:00:00 则在下一天的 10:10:10 首次执行 往后以此类推。配置一次就可以了 只要程序运行着 计划任务就会在指定日期,不论是中途手动退出程序重新开,计算机重启,都无需干预 ,会自动按照计划任务列表里来执行,执行完后会自动刷新下次执行时间 和历史执行记录。代码的注释把以上原理阐述的很清楚。

void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                if (Started == false)//首次添加任务 或者程序刚启动 安排接下来执行时间
                {
                    //进行首次的当天执行
                    DateTime now = DateTime.Now;

                    DateTime actionTime;
                    DateTimeFormatInfo dtfi = new CultureInfo("zh-CN", false).DateTimeFormat;
                    bool convertok = DateTime.TryParseExact(Time, "HH:mm:ss", dtfi, DateTimeStyles.None, out actionTime);
                    if (convertok == false)
                        return;

                    //年月日替换为 创建之日的 //开始时间必须从创建之时 开始算

                    switch (DaysUnit)
                    {
                        case "天"://如果跃迁为天 替换掉 从当天的天开始算(时分秒 以计划设定为准)
                            actionTime = new DateTime(CreateAt.Year, CreateAt.Month, CreateAt.Day, actionTime.Hour, actionTime.Minute, actionTime.Second);
                            break;

                        case "时"://如果跃迁为时 替换掉 从当天的天.时开始算(分秒 以计划设定为准)
                            actionTime = new DateTime(CreateAt.Year, CreateAt.Month, CreateAt.Day, CreateAt.Hour, actionTime.Minute, actionTime.Second);
                            break;

                        case "分"://如果跃迁为分 替换掉 从当天的天.时.分开始算(秒 以计划设定为准)
                            actionTime = new DateTime(CreateAt.Year, CreateAt.Month, CreateAt.Day, CreateAt.Hour, CreateAt.Minute, actionTime.Second);
                            break;
                        default:
                            break;
                    }


                    double interval = (actionTime - now).TotalMilliseconds;
                    if ((actionTime - now).TotalMilliseconds <= 0)//计划时间在当前时间以前
                    {
                        //设置下次执行时间

                        NextActionAt = actionTime;



                        while (interval <= 0)
                        {

                            //如果很久没有启动程序了 加了跃迁 都还是在历史日期

                            //一直加日期 直到加到超过当前日期
                            switch (DaysUnit)
                            {
                                case "天":
                                    actionTime = actionTime.AddDays(Days);
                                    break;

                                case "时":
                                    actionTime = actionTime.AddHours(Days);
                                    break;

                                case "分":
                                    actionTime = actionTime.AddMinutes(Days);
                                    break;
                                default:
                                    break;
                            }

                            interval = (actionTime - now).TotalMilliseconds;
                        }
                    }

                    //此处必定已经累加到interval跃迁大于0了
                    //Console.WriteLine("aaa");
                    if (interval > 0)
                    {
                        NextActionAt = actionTime;

                        timer.Interval = interval;
                        timer.AutoReset = false;
                        timer.Start();


                        Console.WriteLine(ID + "初始化");

                        Started = true;
                    }

                }
                else//运行中
                {
                    DateTime now = DateTime.Now;
                    Console.WriteLine(ID + "执行于:" + NextActionAt.ToString());


                    try
                    {

                        //串口操作
                        ComDevice.Open();
                        byte[] data1 = new byte[] { 0xAA };
                        byte[] data2 = new byte[] { 0xBB };
                        byte[] data3 = new byte[] { 0xCC };

                        ComDevice.Write(data1, 0, 1);
                        Thread.Sleep(Spand1 * 1000);
                        ComDevice.Write(data2, 0, 1);
                        Thread.Sleep(Spand2 * 1000);
                        ComDevice.Write(data3, 0, 1);
                        ComDevice.Close();

                        //操作完成 更新数据库记录 何下次action时间  ,并且设置自身nextActionAt

                        if (historyAdd != null)
                        {
                            History hist = new History();
                            hist.Success = 1;
                            hist.SuccessStr = "成功";
                            hist.ScheduleID = ID;
                            hist.ActionAt = NextActionAt.Value;
                            historyAdd.Invoke(hist);
                        }
                    }
                    catch (Exception ex)
                    {
                        LoggerManager.Instance.WriteLog(ex.Message);
                        if (historyAdd != null)
                        {
                            History hist = new History();
                            hist.Success = 0;
                            hist.SuccessStr = "失败";
                            hist.ScheduleID = ID;
                            hist.ActionAt = NextActionAt.Value;
                            historyAdd.Invoke(hist);
                        }
                    }
                    finally
                    {
                        //无论如何都进行下次的计划任务定制
                        //执行到此处的时候肯定是actionAt时间到了,只需再加上days即可
                        switch (DaysUnit)
                        {
                            case "天":
                                NextActionAt = NextActionAt.Value.AddDays(Days);
                                break;

                            case "时":
                                NextActionAt = NextActionAt.Value.AddHours(Days);
                                break;

                            case "分":
                                NextActionAt = NextActionAt.Value.AddMinutes(Days);
                                break;
                            default:
                                break;
                        }


                        timer.Interval = (NextActionAt.Value - now).TotalMilliseconds;
                        timer.AutoReset = false;
                        timer.Start();
                    }


                }
            }
            catch (Exception ex)
            {

                Console.WriteLine("遇到错误:" + ex.Message);
                LoggerManager.Instance.WriteLog(ex.Message);
            }

        }

在程序启动的时候从access数据库读取记录 然后把所有计划任务都计算并启动一遍。

public void StartSchedule()
{
    //清理以前的
    if (RunningSchedule != null)
    {
        for (int i = 0; i < RunningSchedule.Count; i++)
        {
            RunningSchedule[i].TimerClose();
        }
        RunningSchedule.Clear();
    }
    else
    {
        RunningSchedule = new ObservableCollection<ScheduleItemObj>();
    }


    if (Data == null || Data.Count == 0)
    {
        return;
    }

    for (int i = 0; i < Data.Count; i++)
    {
        RunningSchedule.Add(Data[i]);
        Data[i].historyAdd += new Action<History>((hist) => {

            uiDispatcher.Invoke(new Action(() => {

            this.AddDBHistory(hist);
            this.AddHistoryVirtual(hist);
            }));
        });
        Data[i].Start();
    }
}

 

当然 如果是添加新任务 我们也是很简易粗暴的 添加数据 然后把所有任务停止,停止的时候会回收资源, 然后再启动一遍 ,这样便于我们更简易的控制。

if (sw.ShowDialog() == true)
{
    vm.AddSchedule(sw.day, sw.time.ToString("HH:mm:ss"),sw.daysUnit);
    vm.StartSchedule();
}

然后我们做了一个启动时自动缩小到任务栏托盘运行的方式。我们是根据开始基础日期 的设定进行 跃迁 单位 计算下次执行时间的 而不是累加,所以程序运行多久都不会出现执行时间上的偏差。

当然 有几个东西要知晓 ,1应用程序必须要一直在运行期间计划任务才能够得到成功执行,系统没有登录的情况下不会得到执行。我的运用环境是满足的。 要规避这个问题网上说可以弄成服务形式的。

好了完工,上一个运行图,好,完美,此程序现在已稳定运行相当长一段时间了,未出过问题。

实现一个“计划任务”机制-LMLPHP

 实现一个“计划任务”机制-LMLPHP

 

10-12 11:40