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

 

 

 

posted on 2012-09-21 10:28  Qlin  阅读(3083)  评论(0编辑  收藏  举报