在日常工作中,大家都会经常遇到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中有输出就可以看到效果
这里的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
如果有好的改动或是意见请反馈给我,代码改动后也回传我一份,谢谢