山寨版Quartz.Net任務統一排程框架

韓天偉發表於2015-05-27

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

如果有好的改動或是意見請反饋給我,程式碼改動後也回傳我一份,謝謝

相關文章