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

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

1. 安装TopShelf程序包

在Program中写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 是自定义类<br>

  编译之后在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;
        }

    }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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();
复制代码

 

posted @   小布雷  阅读(1175)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示