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