zoukankan      html  css  js  c++  java
  • [Scheduled Timer]第二回:时间调度

    1.引言

    上一节里简单介绍了Scheduled Timer,也有园友推荐quartz.net,非常感谢他们,这个星期一直在看Scheduled Timer,就继续做笔记记录下来。

    将System.Timers.Timer运行间隔Interval设置时间越短,越精确。这也就是说,Timer计时器将会以间隔很短的时间一直在运行,每次运行都将触发Elapsed事件,但是每次Elapsed事件触发,并不是要触发我们的作业工作。ScheduledTime是时间调度,它将引领事件里添加的任务按需执行,接下来介绍Scheduled Timer的时间调度。

    2.IScheduledItem

    触发Elapsed事件时,这样让里面的方法按需执行呢,如到了9:00就行。ScheduledTime就是要让我们的工作,按照我们的意愿来执行。

    Scheduled Timer的时间调度接口声明如下:

        public interface IScheduledItem
        {
            /// <summary>
            /// 返回 要执行的任务集合
            /// </summary>
            /// <param name="begin">上次执行的时间</param>
            /// <param name="end">当前执行的时间</param>
            /// <param name="list"></param>
            void AddEventsInInterval(DateTime begin, DateTime end, List<DateTime> list);
    
            /// <summary>
            /// 获取下次运行的时间
            /// </summary>
            /// <param name="time">开始计时的时间</param>
            /// <param name="includeStartTime">是否包含开始计时的时间</param>
            /// <returns></returns>
            DateTime NextRunTime(DateTime time, bool includeStartTime);
        }
    IScheduledItem接口只做两件事:
    • 获取一段时间内有效的执行时间段集合
    • 获取下一次执行的具体时间

    现在有一个任务在9:00 执行,怎么才能让这个任务在9:00准时执行呢,一要定时器Timer一直在运行,没有运行的话,那肯定是不会在后台申请线程进行执行任务的;

    二要Timer的Interval间隔要足够短,如果不够短,就可能跳过了9:00,到时就没有准时执行了。

    接下来介绍一下接口方法:

    AddEventsInInterval 方法参考上面的注释,获取结果是 List<DateTime> list,可能有人会问,为什么是一个集合呢?上次执行,到下次执行,就一个任务吧,可能是,如果线程堵塞,就可能延时。

    比如,有一个任务,每个两分钟,发送一封邮件,时间间隔Interval也是设置2分钟,假设上次发送是9:00,由于各种原因,没有按照2分钟来执行,这次执行的时间是9:10,过了10分钟,

    这时,是发送一次,还是说发送5次(9:02,9:04,9:06,9:08,9:10)呢?应该是5次,所以是List集合。

    那这个集合是怎么算的呢,要根据需求来定,比如上面那个每隔两分钟发邮件的,可以这么算,获取上次执行时间,和当前执行时间之间的时间差,再进行和时间间隔2分钟来划分。

    NextRunTime 方法,获取下次运行的时间,有两个作用,一是可以用于AddEventsInInterval方法,每次获取这个时间,再和 当前时间对比,小于当前时间者,则是有效执行时间;

    二可以动态设置定时器Timer的Interrval的间隔,执行一次时,可以设置Interval,如果下次执行时间是一分钟后,我们就可以改变Interval,而用不着设置10秒了。

    3.ScheduledTime

    ScheduledTime IScheduledItem的一个实现,直接上代码:

        /// <summary>
        /// 预定周期枚举
        /// </summary>
        public enum EventTimeUnit
        {
            //
            BySecond = 1,
            //
            ByMinute = 2,
            //小时
            Hourly = 3,
            //
            Daily = 4,
            //
            Weekly = 5,
            //
            Monthly = 6
        }
        [Serializable]
        public class ScheduledTime : IScheduledItem
        {
            /// <summary>
            /// 间隔单位
            /// </summary>
            private EventTimeUnit _base;
            /// <summary>
            /// 具体时间
            /// </summary>
            private TimeSpan _offset;
    
            public ScheduledTime(EventTimeUnit etBase, TimeSpan offset)
            {
                this._base = etBase;
                this._offset = offset;
            }
    /// <summary>
            /// 添加事件时间
            /// </summary>
            /// <param name="begin"></param>
            /// <param name="end"></param>
            /// <param name="list"></param>
            public void AddEventsInInterval(DateTime begin, DateTime end, List<DateTime> list)
            {
                DateTime next = NextRunTime(begin, true);
    
                while (next < end)
                {
                    list.Add(next);
                    next = IncInterval(next);
                }
            }
    
            /// <summary>
            /// 获取下次执行时间
            /// </summary>
            /// <param name="time"></param>
            /// <param name="allowExact"></param>
            /// <returns></returns>
            public DateTime NextRunTime(DateTime time, bool allowExact)
            {
                //获取 由time开始起的下次执行时间
                DateTime nextRun = LastSyncForTime(time) + _offset;
    
                if (nextRun == time && allowExact)
                    return time;
                else if (nextRun > time)
                    return nextRun;
                else
                    return IncInterval(nextRun);
            }
    
    
            private DateTime LastSyncForTime(DateTime time)
            {
                switch (_base)
                {
                    case EventTimeUnit.BySecond:
                        return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second);
                    case EventTimeUnit.ByMinute:
                        return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, 0);
                    case EventTimeUnit.Hourly:
                        return new DateTime(time.Year, time.Month, time.Day, time.Hour, 0, 0);
                    case EventTimeUnit.Daily:
                        return new DateTime(time.Year, time.Month, time.Day);
                    case EventTimeUnit.Weekly:
                        return (new DateTime(time.Year, time.Month, time.Day)).AddDays(-(int)time.DayOfWeek);
                    case EventTimeUnit.Monthly:
                        return new DateTime(time.Year, time.Month, 1);
                }
                throw new Exception("Invalid base specified for timer.");
            }
    
            /// <summary>
            /// 增加单位间隔
            /// </summary>
            /// <param name="last"></param>
            /// <returns></returns>
            private DateTime IncInterval(DateTime last)
            {
                switch (_base)
                {
                    case EventTimeUnit.BySecond:
                        return last.AddSeconds(1);
                    case EventTimeUnit.ByMinute:
                        return last.AddMinutes(1);
                    case EventTimeUnit.Hourly:
                        return last.AddHours(1);
                    case EventTimeUnit.Daily:
                        return last.AddDays(1);
                    case EventTimeUnit.Weekly:
                        return last.AddDays(7);
                    case EventTimeUnit.Monthly:
                        return last.AddMonths(1);
                }
                throw new Exception("Invalid base specified for timer.");
            }      
        }

    接下来进行测试

            [TestMethod]
            public void HourlyTest()
            {
                //按小时周期
                IScheduledItem item = new ScheduledTime(EventTimeUnit.Hourly, TimeSpan.FromMinutes(20));
                //起始时间为 2004-01-01 00:00:00,包含起始时间,下个执行时间为:2004-01-01 00:20:00
                TestItem(item, new DateTime(2004, 1, 1), true, new DateTime(2004, 1, 1, 0, 20, 0));
    
                //起始时间为 2004-01-01 00:00:00,不包含起始时间,下个执行时间为:2004-01-01 00:20:00
                TestItem(item, new DateTime(2004, 1, 1), false, new DateTime(2004, 1, 1, 0, 20, 0));
    
                //起始时间为 2004-01-01 00:20:00,包含起始时间,下个执行时间为:2004-01-01 00:20:00
                TestItem(item, new DateTime(2004, 1, 1, 0, 20, 0), true, new DateTime(2004, 1, 1, 0, 20, 0));
    
                //起始时间为 2004-01-01 00:20:00,不包含起始时间,下个执行时间为:2004-01-01 01:20:00
                TestItem(item, new DateTime(2004, 1, 1, 0, 20, 0), false, new DateTime(2004, 1, 1, 1, 20, 0));
    
                //起始时间为 2004-01-01 00:20:01,包含起始时间,下个执行时间为:2004-01-01 01:20:00
                TestItem(item, new DateTime(2004, 1, 1, 0, 20, 1), true, new DateTime(2004, 1, 1, 1, 20, 0));
            }
            private static void TestItem(IScheduledItem item, DateTime input, bool AllowExact, DateTime ExpectedOutput)
            {
                DateTime Result = item.NextRunTime(input, AllowExact);
                //断言
                Assert.AreEqual(ExpectedOutput, Result);           
            }

    测试 为了显示方便,就没有把 每种情况,分开写,都写在一起,有注释。其它 按分钟、天、周、月的类似,就不重复了。

    4.总结

    Scheduled Timer的具体的时间调度,都可以实现IScheduledItem接口,大家可以根据自己的需求来实现。Scheduled Timer的时间的计算算是一个比较核心的工作,IScheduledItem设计的也比较巧妙。

     

  • 相关阅读:
    控制翻转与容器
    构造函数传递参数
    bean属性检查
    tomcat源码阅读14
    Block Formatting Context
    IE 兼容性问题的处理
    JavaScript 的原型与继承
    IE 多版本测试工具 IETester
    callee,caller,call,apply
    HDOJ2175 汉诺塔IX
  • 原文地址:https://www.cnblogs.com/qqlin/p/2687380.html
Copyright © 2011-2022 走看看