一、背景
工作中经常涉及任务调度,一直都是采用while(true) => if hitted DO => Thread.Sleep(interval)的模式。但是最近实在是感觉这种实现模式很挫。并且没有考虑到性能问题,需要撞击n次才能命中一次,使用效率不足5%(一百次while循环命中不到5次),但是单方面加大线程睡眠时间又无法保证高准确性和高精度。那有么有其它好的思路:即可以保持高准确性、高精度,又不浪费资源呢?
二、我的思路
上述的短板在于:无目的的线程Sleep,如果我们可以每次恰到好处的Sleep,即线程被唤醒后刚好赶上下一次的任务到来,命中率就是100%了嘛。所以可以这样做:进入While后先按照Scheduler计算出下次的运行时间距离现在还有多久(Interval),然后直接Sleep(Interval),等待线程唤醒后就立即执行下一个既定Task就可以了。那这样做唯一的难点就在于你的Scheduler是否可计算、是否可以计算出下一次的运行时间点。
还好,我碰到的逻辑都还是满足这一点需求的:
(1)每隔一段时间执行一次的计划任务;
(2)在指定时间点必须执行的计划任务;
三、代码实现
(1)主要有:负责调度的接口:IScheduler;Scheduler设定:ISchedulerSetting;任务接口:ITask以及一些其他的辅助类。
(2)难点:计算Interval的实现类:FixTimeSchedulerSetting.cs和IntervalSchedulerSetting.cs
class="brush:csharp;gutter:true;">//Author: night-king // Email: deepleo@163.com //Home: http://deepleo.com //Github: https://github.com/night-king/ //Date: 2013-11-1 //Place: Wuhan@China using System; using System.Collections.Generic; using System.Text; using System.Collections; namespace Deepleo.LazyScheduler.Setting { public class FixTimeSchedulerSetting : IFixTimeSchedulerSetting, IWeekSchedulerSetting { public IList<DayOfWeek> ExcludeWeeks { get; set; } public int Hour { get; set; } public int Minutes { get; set; } public int Second { get; set; } public FixTimeSchedulerSetting(IList<DayOfWeek> excludeWeeks) { ExcludeWeeks = excludeWeeks; } public virtual TimeSpan CalculateNext() { var nowDateTime = System.DateTime.Now; var expectNextTime = System.DateTime.Parse(string.Format("{0}-{1}-{2} {3}:{4}:{5}", nowDateTime.Year, nowDateTime.Month, nowDateTime.Day, Hour, Minutes, Second)); var todayWeek = nowDateTime.DayOfWeek; var km = new WeekManager(ExcludeWeeks); var delayDays = km.CalculateDelayDays(todayWeek); if (delayDays == 0)// this day of week can do { if (expectNextTime > nowDateTime) return expectNextTime - nowDateTime; else delayDays++; } return expectNextTime.AddDays(delayDays) - nowDateTime; } } }
using System; using System.Collections.Generic; using System.Text; namespace Deepleo.LazyScheduler.Setting { public class IntervalSchedulerSetting : IIntervalSchedulerSetting, IWeekSchedulerSetting { public TimeSpan Interval { get; set; } public IList<DayOfWeek> ExcludeWeeks { get; set; } public IntervalSchedulerSetting(IList<DayOfWeek> excludeWeeks) { ExcludeWeeks = excludeWeeks; } public TimeSpan CalculateNext() { var nowDateTime = System.DateTime.Now; var todayWeek =nowDateTime.DayOfWeek; var km = new WeekManager(ExcludeWeeks); var delayDays = km.CalculateDelayDays(todayWeek); var interval = Interval.Add(new TimeSpan(delayDays, 0, 0, 0)); if (interval.CompareTo(new TimeSpan(0, 0, 1)) <= 0) return new TimeSpan(0, 0, 1); else return interval; } } }
重要的辅助类:WeekManager的实现代码:
//Author: night-king // Email: deepleo@163.com //Home: http://deepleo.com //Github: https://github.com/night-king/ //Date: 2013-11-1 //Place: Wuhan@China using System; using System.Collections.Generic; using System.Text; namespace Deepleo.LazyScheduler.Setting { public class WeekManager { private Dictionary<int, DayOfWeek> _allowWeekDict; public WeekManager(IList<DayOfWeek> notAllowWeeks) { _allowWeekDict = new Dictionary<int, DayOfWeek>(); if (!notAllowWeeks.Contains(DayOfWeek.Sunday)) _allowWeekDict.Add(0, DayOfWeek.Sunday); if (!notAllowWeeks.Contains(DayOfWeek.Monday)) _allowWeekDict.Add(1, DayOfWeek.Monday); if (!notAllowWeeks.Contains(DayOfWeek.Tuesday)) _allowWeekDict.Add(2, DayOfWeek.Tuesday); if (!notAllowWeeks.Contains(DayOfWeek.Wednesday)) _allowWeekDict.Add(3, DayOfWeek.Wednesday); if (!notAllowWeeks.Contains(DayOfWeek.Thursday)) _allowWeekDict.Add(4, DayOfWeek.Thursday); if (!notAllowWeeks.Contains(DayOfWeek.Friday)) _allowWeekDict.Add(5, DayOfWeek.Friday); if (!notAllowWeeks.Contains(DayOfWeek.Saturday)) _allowWeekDict.Add(6, DayOfWeek.Saturday); } /// <summary> /// Calcute how many delay days /// </summary> /// <param name="week">current day of week</param> /// <returns>delay days</returns> public int CalculateDelayDays(DayOfWeek week) { var weekOfDay = (int)week; int distence = 0; while (true) { if (_allowWeekDict.ContainsKey(weekOfDay)) { return distence; } else { weekOfDay = weekOfDay < 6 ? weekOfDay + 1 : 0; distence++; } } } } }
四、测试
(1)5s钟时间间隔的测试结果:
(2)1s测试结果:
再看CPU占用情况:
基本上稳定在0%,内存占用也只有6.4k.
五、代码下载:
http://files.cnblogs.com/deepleo/SchedulerSolution.zip
https://github.com/night-king/LazyScheduler