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