Quartz + Topshelf 实现多任务调度的Windows服务

时间:2023-01-13 20:01:06

Topshelf实现对于控制台程序安装windows服务

1. 安装TopShelf程序包

在Program中写入:

var host = HostFactory.New(x =>
            {
                x.Service<QuartzServiceRunner>(s =>
                {
                    s.ConstructUsing(name => new ServiceTask());
                    s.WhenStarted(p => p.Start());
                    s.WhenStopped(p => p.Stop());
                });

                x.RunAsLocalSystem();
                x.SetDescription("Bley_QuartzTopShelf_Service");
                x.SetDisplayName("QuartzTopShelfDemo服务");
                x.SetServiceName("QuartzTopShelfDemoService");
            });

            host.Run();
          QuartzServiceRunner 是自定义类

  编译之后在bin下面的exe 文件 利用 cmd 去安装  ***.exe install    卸载 ***.exe uninstall (安装卸载比较方便)

 

2. Quartz 的使用:

利用nuget对Quartz包进行安装

Quartz Corn的表达式用法:

由7段构成:秒 分 时 日 月 星期 年(可选) 七段之间用空格隔开

"-" :表示范围 MON-WED表示星期一到星期三
"," :表示列举 MON,WEB表示星期一和星期三
"*" :表是“每”,每月,每天,每周,每年等
"/" :表示增量:0/15(处于分钟段里面) 每15分钟,在0分以后开始,3/20 每20分钟,从3分钟以后开始
"?" :只能出现在日,星期段里面,表示不指定具体的值
"L" :只能出现在日,星期段里面,是Last的缩写,一个月的最后一天,一个星期的最后一天(星期六)
"W" :表示工作日,距离给定值最近的工作日
"#" :表示一个月的第几个星期几,例如:"6#3"表示每个月的第三个星期五(1=SUN...6=FRI,7=SAT)

 

Quartz的几个概念:

IScheduler 任务调度器

IJobDetail  Ijob 任务

ITrigger Trigger 触发器

单个Job的执行:

  <appSettings>
    <add key="cronExpr" value="0/5 * * * * ?"/>
  </appSettings>
    public class QuartzServiceRunner
    {
        private readonly IScheduler scheduler;

        public QuartzServiceRunner()
        {
            scheduler = StdSchedulerFactory.GetDefaultScheduler();
        }

        public void Start()
        {
            //从配置文件中读取任务启动时间
            string cronExpr = ConfigurationManager.AppSettings["cronExpr"];
            IJobDetail job = JobBuilder.Create<DeleteDomainJob>().WithIdentity("job1", "group1").Build();
            //创建任务运行的触发器
            ITrigger trigger = TriggerBuilder.Create()
                .WithIdentity("triggger1", "group1")
                .WithSchedule(CronScheduleBuilder.CronSchedule(new CronExpression(cronExpr)))
                .Build();
            //启动任务
            scheduler.ScheduleJob(job, trigger);
            scheduler.Start();

        }

        public void Stop()
        {
            scheduler.Clear();
        }

        public bool Continue(HostControl hostControl)
        {
            scheduler.ResumeAll();
            return true;
        }

        public bool Pause(HostControl hostControl)
        {
            scheduler.PauseAll();
            return true;
        }

    }
    public class DeleteDomainJob : IJob
    {
        //readonly ILog _log = LogManager.GetLogger(typeof(DeleteDomainJob));
        public void Execute(IJobExecutionContext context)
        {

            NlogHelper.LogInfo("start job -----------------------");

            System.Threading.Thread.Sleep(1000 * 3);

            NlogHelper.LogInfo("end job -------------------------");

        }
    }
    public class DeleteDomainJob : IJob
    {
        //readonly ILog _log = LogManager.GetLogger(typeof(DeleteDomainJob));
        public void Execute(IJobExecutionContext context)
        {

            NlogHelper.LogInfo("start job -----------------------");

            System.Threading.Thread.Sleep(1000 * 3);

            NlogHelper.LogInfo("end job -------------------------");

        }
    }

 

对于Job 我们一般肯定是希望可以同时在这个项目中配置出多个job

可以考虑做一个JobCollection类来封装配置文件

配置文件如下:

  <configSections>
    <section name="JobSettings" type="ServiceJobHosts.JobSettings,ServiceJobHosts"/>
  </configSections>
  <JobSettings>
    <add type="ServicesJobs.GuessSendPrizeJob, ServicesJobs" cron="0/5 * * * * ?" triggerInstantly="true"/>
    <add type="ServicesJobs.LottoSendPrizeJob, ServicesJobs" cron="0/5 * * * * ?" triggerInstantly="true"/>
  </JobSettings>
    public class GuessSendPrizeJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            NlogHelper.LogInfo("Start Guess Send Prize Job--------------");


            System.Threading.Thread.Sleep(10000);


            NlogHelper.LogInfo("End Guess Send Prize Job  --------------");
        }
    }
    public class LottoSendPrizeJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            //log start 

            NlogHelper.LogError("Start Lotto Send Prize Job--------------");


            System.Threading.Thread.Sleep(10000);


            NlogHelper.LogError("End Lotto Send Prize Job  --------------");

        }
    }
    public class JobWrapper
    {
        /// <summary>
        /// 任务详情
        /// </summary>
        public IJobDetail JobDetail { get; set; }
        /// <summary>
        /// 任务触发器
        /// </summary>
        public ITrigger Trigger { get; set; }

        /// <summary>
        /// 是否立即触发
        /// </summary>
        public bool TriggerInstantly { get; set; }

    }
    /// <summary>
    /// 定时任务配置类
    /// </summary>
    public class JobSettings : ConfigurationSection
    {
        [ConfigurationProperty("", IsDefaultCollection = true)]
        public JobTypeElementCollection JobTypes
        {
            get { return (JobTypeElementCollection)this[""]; }
        }

        public static JobSettings GetSection()
        {
            return ConfigurationManager.GetSection("JobSettings") as JobSettings;
        }
    }

    /// <summary>
    /// 定时任务配置集合
    /// </summary>
    public class JobTypeElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new JobTypeElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            JobTypeElement serviceTypeElement = (JobTypeElement)element;
            return serviceTypeElement.JobType.MetadataToken;
        }
    }

    /// <summary>
    /// 单个定时任务配置类
    /// </summary>
    public class JobTypeElement : ConfigurationElement
    {
        /// <summary>
        /// cron表达式
        /// </summary>
        [ConfigurationProperty("cron", IsRequired = true)]
        public string CronExpression
        {
            get { return this["cron"] as string; }
            set { this["cron"] = value; }
        }

        /// <summary>
        /// 是否立即触发
        /// </summary>
        [ConfigurationProperty("triggerInstantly", IsRequired = true)]
        public bool TriggerInstantly
        {
            get { return (bool)this["triggerInstantly"]; }
            set { this["triggerInstantly"] = value; }
        }

        /// <summary>
        /// 定时job的类型
        /// </summary>
        [ConfigurationProperty("type", IsRequired = true)]
        [TypeConverter(typeof(AssemblyQualifiedTypeNameConverter))]
        public Type JobType
        {
            get { return (Type)this["type"]; }
            set { this["type"] = value; }
        }
    }
    public class AssemblyQualifiedTypeNameConverter : ConfigurationConverterBase
    {
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            Type result = null;
            string typeName = value as string;
            if (!string.IsNullOrWhiteSpace(typeName))
            {
                result = Type.GetType(typeName, false);
                if (result == null)
                {
                    throw new ArgumentException(string.Format("不能加载类型\"{0}\"", typeName));
                }
            }
            return result;
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            Type type = value as Type;
            if (type == null)
            {
                throw new ArgumentNullException("value");
            }
            return type.AssemblyQualifiedName;
        }

    }
    public class JobCollection : Collection<JobWrapper>
    {
        #region Fields & Properties

        /// <summary>
        /// 作业调度器工厂
        /// </summary>
        private static ISchedulerFactory schedulerFactory;

        /// <summary>
        /// 作业调度器
        /// </summary>
        private static IScheduler scheduler;

        #endregion

        private class Nested
        {
            static Nested() { }
            internal static readonly JobCollection Instance = new JobCollection();
        }

        public static JobCollection Instance
        {
            get { return Nested.Instance; }
        }

        /// <summary>
        /// 静态构造器
        /// </summary>
        static JobCollection()
        {
            try
            {
                schedulerFactory = new StdSchedulerFactory();
                scheduler = schedulerFactory.GetScheduler();
            }
            catch(Exception ex)
            {

                int m = 2;
            }

        }


        /// <summary>
        /// 私有构造器
        /// </summary>
        /// <param name="serviceTypes"></param>
        private JobCollection()
        {
            JobSettings settings = JobSettings.GetSection();
            if (settings == null)
            {
                //Log.WriteLog("未能获取到定时任务配置信息!");
                return;
            }

            foreach (JobTypeElement element in settings.JobTypes)
            {
                if (string.IsNullOrWhiteSpace(element.CronExpression))
                {
                    continue;
                }
                try
                {
                    string jobName = element.JobType.Name;
                    JobKey jobKey = new JobKey("job_" + jobName);
                    IJobDetail jobDetail = JobBuilder.Create(element.JobType)
                                            .WithIdentity(jobKey)
                                            .Build();

                    //触发器
                    /* WithMisfireHandlingInstructionDoNothing()方法与DisallowConcurrentExecution特性配合使用,
                     * 可以起到如下作用:同一Job如果上一轮执行还未完成,则本次不触发。
                     */
                    ITrigger trigger = TriggerBuilder.Create()
                                        .WithIdentity(string.Format("trigger_{0}", jobName))
                                        .WithSchedule(CronScheduleBuilder.CronSchedule(element.CronExpression).WithMisfireHandlingInstructionDoNothing())
                                        .Build();

                    JobWrapper job = new JobWrapper { JobDetail = jobDetail, Trigger = trigger, TriggerInstantly = element.TriggerInstantly };
                    this.Add(job);
                }
                catch (Exception ex)
                {
                    string msg = string.Format("加载定时任务{0}时发生异常:{1}", element.JobType.Name, ex.Message);
                    //Log.WriteLog(msg);
                }
            }
        }

        /// <summary>
        /// 启动集合中所有定时任务
        /// </summary>
        public void Start()
        {
            scheduler.Start();

            foreach (JobWrapper job in this)
            {
                try
                {
                    scheduler.ScheduleJob(job.JobDetail, job.Trigger);
                    if (job.TriggerInstantly)
                    {
                        scheduler.TriggerJob(job.JobDetail.Key);
                    }
                    //File.AppendAllText(sysLogPath, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + "服务" + job.JobDetail.Key.Name + "顺利启动 \r\n");
                }
                catch (Exception ex)
                {
                    //File.AppendAllText(sysLogPath, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + ex.Message + "\r\n" + ex.StackTrace + "\r\n");
                }
            }
        }

        /// <summary>
        /// 关闭集合中所有定时任务
        /// </summary>
        public void Stop()
        {
            foreach (JobWrapper job in this)
            {
                try
                {
                    JobKey jobKey = job.JobDetail.Key;
                    string treggerName = "trigger_" + jobKey.Name.Substring(jobKey.Name.IndexOf("_") + 1);
                    TriggerKey triggerKey = new TriggerKey(treggerName);
                    scheduler.PauseTrigger(triggerKey);
                    scheduler.UnscheduleJob(triggerKey);
                    scheduler.DeleteJob(jobKey);
                    //File.AppendAllText(sysLogPath, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + "服务" + job.JobDetail.Key.Name + "已经停止 \r\n");
                }
                catch (Exception ex)
                {
                    //File.AppendAllText(sysLogPath, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + ex.Message + "\r\n" + ex.StackTrace + "\r\n");
                }
            }
            scheduler.Shutdown(false);
        }



    }

 

  public class ServiceTask
    {
        public void Start()
        {
            JobCollection.Instance.Start();
        }

        public void Stop()
        {
            JobCollection.Instance.Stop();
        }
    }

 

在Program.cs中加入如下代码:

            var host = HostFactory.New(x =>
            {
                x.Service<ServiceTask>(s =>
                {
                    s.ConstructUsing(name => new ServiceTask());
                    s.WhenStarted(p => p.Start());
                    s.WhenStopped(p => p.Stop());
                });

                x.RunAsLocalSystem();
                x.SetDescription("Bley_QuartzTopShelf_Service");
                x.SetDisplayName("QuartzTopShelfDemo服务");
                x.SetServiceName("QuartzTopShelfDemoService");
            });

            host.Run();