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();
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 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)