[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设计的也比较巧妙。

 

 

 

你可能感兴趣的:(scheduled)