一、背景
工作中經常涉及任務調度,一直都是采用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
//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