山寨版Quartz.Net任務統一調度框架
TaskScheduler
在日常工作中,大家都會經常遇到Win服務,在我工作的這些年中一直在使用Quartz.Net這個任務統一調度框架,也非常好用,配置簡單,但是如果多個項目組的多個服務部署到一台服務器時還是不盡如人意。
這段時間很忙,也一直未更新博客了,趕上今天下班早,就研究了一下,弄了個簡單版基於Timer的山寨Quartz,當然了只是實現任務調度,閑話少說直接入主題吧
一、技術准備
其實都是普通的微軟技術,一想到這方我們第一想到的可能就是反射,本文用了MEF
二、框架搭建
第一我們建立項目TianWei.TaskScheduler
第二我們會想到給Timer加個參數,這里建了一個 TWTimer來繼承Timer,在里面有一個屬性為JobDetail(Job詳情實本),這樣每個TImer我們就可以把任務詳情做為參數傳入
/// <summary>
/// 自定義Timer
/// </summary>
public class TWTimer : System.Timers.Timer
{
public JobDetail JobDetail { get; set; }
}
第三建立JobDetail
/// <summary>
/// 作業請情
/// </summary>
[XmlRootAttribute("xml", IsNullable = false)]
public class JobDetail
{
/// <summary>
/// 作業名稱
/// </summary>
public string Name { get; set; }
/// <summary>
/// 作業執行類
/// </summary>
public string JobType { get; set; }
/// <summary>
/// 自定義Cron表達式
/// </summary>
public string CronExpression { get; set; }
/// <summary>
/// 作業類型
/// </summary>
[XmlIgnoreAttribute]
public WorkType WorkType { get; set; }
/// <summary>
/// 如果是每周 周幾
/// </summary>
[XmlIgnoreAttribute]
public DayOfWeek Week { get; set; }
/// <summary>
/// 執行表達式
/// </summary>
[XmlIgnoreAttribute]
public string ExecuteExpression { get; set; }
/// <summary>
/// 執行間隔 循環執行有效
/// </summary>
[XmlIgnoreAttribute]
public int Interval { get; set; }
/// <summary>
/// 作業狀態 停啟用
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// 作業開始工作時間- 可為空
/// </summary>
public string StartTime { get; set; }
/// <summary>
/// 作業結束時間-可為空
/// </summary>
public string EndTime { get; set; }
/// <summary>
/// 作業開始工作時間-默認為最小時間
/// </summary>
[XmlIgnoreAttribute]
public DateTime JobStartTime
{
get
{
DateTime value = DateTime.MinValue;
if (StartTime != null)
{
DateTime.TryParse(StartTime, out value);
}
return value;
}
set { }
}
/// <summary>
/// 作業結束工作時間-默認為最大時間
/// </summary>
[XmlIgnoreAttribute]
public DateTime JobEndTime
{
get
{
DateTime value = DateTime.MaxValue;
if (EndTime != null)
{
DateTime.TryParse(EndTime, out value);
}
return value;
}
set { }
}
}
第四建立Job作為根據參數判斷執行哪個Job
public class Job
{
public void Execute(JobDetail jobDetail, IJob job)
{
if (!jobDetail.Enabled) return;
if (DateTime.Now < jobDetail.JobStartTime || DateTime.Now > jobDetail.JobEndTime) return;
if (jobDetail.WorkType == WorkType.Week)
{
if (jobDetail.Week == DateTime.Now.DayOfWeek && jobDetail.ExecuteExpression == DateTime.Now.ToString("HHmmss"))
{
job.Execute();
}
}
else if (jobDetail.WorkType == WorkType.Yearly)
{
if (jobDetail.ExecuteExpression == DateTime.Now.ToString("MMddHHmmss"))
{
job.Execute();
}
}
else if (jobDetail.WorkType == WorkType.Monthly)
{
if (jobDetail.ExecuteExpression == DateTime.Now.ToString("ddHHmmss"))
{
job.Execute();
}
}
else if (jobDetail.WorkType == WorkType.Daily)
{
if (jobDetail.ExecuteExpression == DateTime.Now.ToString("HHmmss"))
{
job.Execute();
}
}
else if (jobDetail.WorkType == WorkType.Loop)
{
job.Execute();
}
}
}
第五建立接口IJob,所有Job都要繼承並實現Execute
/// <summary>
/// 作業接口
/// </summary>
public interface IJob
{
/// <summary>
/// 作業需要繼承的接口
/// </summary>
void Execute();
}
第六建立核心部分調度器,這里用到了MEF的導入和導出
public class Scheduler
{
[ImportMany(typeof(IJob))]
public List<IJob> jobs;
public Dictionary<string, IJob> dicJobs;
public Dictionary<string, TWTimer> dicTimer;
private void Run()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
public void Execute()
{
Run();
SetDicJobs();
SetDicTimers();
FileWatcher();
}
private void SetDicJobs()
{
if (jobs != null)
{
dicJobs = new Dictionary<string, IJob>();
foreach (var job in jobs)
{
dicJobs.Add(job.ToString(), job);
}
}
}
private void SetDicTimers()
{
dicTimer = new Dictionary<string, TWTimer>();
var jobList = (List<JobDetail>)XmlHelper.XmlDeserialize(typeof(List<JobDetail>), Config.ConfigPath);
if (jobList != null)
{
foreach (var item in jobList)
{
SetTimer(item);
}
}
}
/// <summary>
/// Timer
/// </summary>
/// <param name="jobDetail"></param>
private void SetTimer(JobDetail jobDetail)
{
TWTimer timer = new TWTimer();
timer.JobDetail = CronHelper.SetCron(jobDetail);
if (timer.JobDetail.WorkType == WorkType.Loop)
{
timer.Interval = timer.JobDetail.Interval;
}
else
{
timer.Interval = 1000;
}
timer.AutoReset = true;
timer.Enabled = true;
timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
dicTimer.Add(timer.JobDetail.Name, timer);
}
/// <summary>
/// Timer事件
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
try
{
var timer = (TWTimer)source;
if (dicJobs.Any(o => o.Key == timer.JobDetail.JobType))
{
Job job = new Job();
job.Execute(timer.JobDetail, dicJobs[timer.JobDetail.JobType]);
}
}
catch (Exception ex)
{
//記錄日志
}
}
/// <summary>
/// 文件監聽
/// </summary>
private void FileWatcher()
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = Environment.CurrentDirectory;
watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = Config.ConfigFile;
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
/// <summary>
/// 文件改動事件
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void OnChanged(object source, FileSystemEventArgs e)
{
var jobList = (List<JobDetail>)XmlHelper.XmlDeserialize(typeof(List<JobDetail>), Config.ConfigPath);
if (jobList != null)
{
foreach (var item in jobList)
{
if (dicTimer.Any(o => o.Key == item.Name))
{
var timer = dicTimer[item.Name];
if (item.JobType != timer.JobDetail.JobType || item.CronExpression != timer.JobDetail.CronExpression)
{
timer.JobDetail = CronHelper.SetCron(item);
if (timer.JobDetail.WorkType == WorkType.Loop)
{
timer.Interval = timer.JobDetail.Interval;
}
else
{
timer.Interval = 1000;
}
}
timer.JobDetail.Enabled = item.Enabled;
timer.JobDetail.StartTime = item.StartTime;
timer.JobDetail.EndTime = item.EndTime;
}
else
{
SetTimer(item);
}
}
}
}
}
其它輔助類詳見源碼
三、使用方法
到這里一個任務調度框架的核心就完成了,下面我信介紹怎么使用
第一在我們想要用到的項目要填加引用TianWei.TaskScheduler
第二在想做為任務的類繼承IJob並實現Execute方法並在類上面加上[Export(typeof(IJob))]
第三在服務程序或控制台程序中引用相關類(這里以控制台程序測試)
第四增加配置文件在App.config中增加<add key="JobsConfig" value="\Jobs.config"/> 在Jobs.config中增加如下配置一個任務一個JobDetail
<?xml version="1.0"?>
<ArrayOfJobDetail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<JobDetail>
<Name>Job1</Name>
<JobType>TianWei.TaskScheduler.Jobs.Job1</JobType>
<CronExpression>1 00 00 * * *</CronExpression>
<Enabled>true</Enabled>
<StartTime>2015-05-1 12:00:00</StartTime>
<EndTime>2015-05-30 12:00:00</EndTime>
</JobDetail>
<JobDetail>
<Name>Job2</Name>
<JobType>TianWei.TaskScheduler.Jobs.Job2</JobType>
<CronExpression>05 37 14 0 * *</CronExpression>
<Enabled>true</Enabled>
<StartTime></StartTime>
<EndTime>2015-05-30 12:00:00</EndTime>
</JobDetail>
<JobDetail>
<Name>Job3</Name>
<JobType>TianWei.TaskScheduler.Jobs.Job3</JobType>
<CronExpression>06 36 14 * * 2</CronExpression>
<Enabled>true</Enabled>
<StartTime>2015-05-20 12:00:00</StartTime>
<EndTime>2015-05-30 12:00:00</EndTime>
</JobDetail>
<JobDetail>
<Name>Job4</Name>
<JobType>TianWei.TaskScheduler.Jobs.Job3</JobType>
<CronExpression>08 35 14 26 05 *</CronExpression>
<Enabled>true</Enabled>
<StartTime>2015-05-20 12:00:00</StartTime>
<EndTime>2015-05-30 12:00:00</EndTime>
</JobDetail>
</ArrayOfJobDetail>
<!--自定義Cron 秒 分 時 日 月 周 當周不為*時 月和日不生效-->
第五增加如下代碼來初使化
static void Main(string[] args)
{
Scheduler sc = new Scheduler();
sc.Execute();
Console.ReadKey();
}
第六運行程序如果Job中有輸出就可以看到效果
四、自定義Crom解釋
這里的Cron表達式也是個山寨的,自定義的,本想解析Quartz的表達式,但是感覺太復雜了
表達式一共六位組成
第一位:秒 只能是0-59或*
第二位:分 只能是0-59或*
第三位:小時 只能是0-24或*
第四位:日 只能是0-31或* 每天執行為0
第五位:月 只能是0-12或*
第六位:周 只能是0-6或*
注:當第六位不為*時第三四五位失效
例:
5 0 0 * * * 每隔五秒執行
5 2 1 * * * 每隔一小時兩分鍾五秒執行
5 37 14 0 * * 每天的14:37:5執行
6 36 14 * * 2 每周二的14:36:6執行
6 36 14 20 6 * 每年6月20號14:36:6執行
6 36 14 20 0 * 每月20號14:36:6執行
代碼地址:https://github.com/hantianwei/TaskScheduler
如果有好的改動或是意見請反饋給我,代碼改動后也回傳我一份,謝謝

