在.NET CORE 3中使用Quartz.NET与Topshelf
之前做windows服务,使用的是Timer计时器来开发,做简单的事情也还行,但做复杂的,还是有点麻烦,所以考虑用Topshelf与Quartz.NET来简化一下。
Quartz.NET是一个强大、开源、轻量的作业调度框架,在项目中用来处理后台处理的任务,例如定时发送邮件通知、后台处理耗时的数据处理等,但在IIS部署的网站中应当注意应用程序池回收的问题。在所有.NET环境中都可以执行,包括但不限于winform、wpf、asp.net webform、asp.net mvc、控制台应用程序、windows服务,.net core等等
创建一个.net core 控制台应用程序,用NuGet包管理器安装Quartz、Topshelf、Topshelf.Log4Net,如下图
先上一个Quartz的基本写法
internal class Program { static async Task Main(string[] args) { //创建作业调度池 var factory = new StdSchedulerFactory(); var scheduler = await factory.GetScheduler(); //创建出一个具体的作业 var job = JobBuilder.Create<GreetingJob>().Build(); //配置一个触发器 var trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithSimpleSchedule(x => x.WithIntervalInSeconds(3).WithRepeatCount(int.MaxValue)).Build(); //加入作业调度池中 await scheduler.ScheduleJob(job, trigger); //开始运行 await scheduler.Start(); Console.ReadKey(); } } [DisallowConcurrentExecution] //禁止并发执行 public class GreetingJob : IJob { //private readonly ILog _logger = LogManager.GetLogger(typeof(GreetingJob)); public Task Execute(IJobExecutionContext context) { Console.WriteLine($"{DateTime.Now}:你好吗?----From GreetingJob"); //_logger.Info($"{DateTime.Now}:你好吗?----From GreetingJob"); Thread.Sleep(3000); return Task.FromResult(true); } }
跑起来就是这个造型,3秒执行一次
在Quartz.net中有两种触发器类型,一种是简单触发器,也就是上面的ISimpleTrigger接口,还有一种是Cron类型的触发器,对应的接口名是ICronTrigger,他使用cron表达式来描述Job的触发事件,这样可以处理更加复杂一些的需求,比如要每天在特定时间点执行(上午 10:00 及 下午 6:00),或者是特定日期执行 (每月5号) 等特殊需求,而这个就是 Quartz.Net 的强项啦,透过其 cron 来描述作业被触发的週期,从秒、分、时、日、月、星期、年都可以进行操作。
"10,20,25 * * * * ? *":每分钟的第10、20、25秒会执行
"10 0/5 * * * ?":每5分钟的第10秒会执行 (ex. 10:00:10 am, 10:05:10 am ...)
"0 20 10-13 ? * WED,FRI":每星期三与星期五的 10:20, 11:20, 12:20, 13:20 执行
"0 0/30 8-9 5,20 * ?":每月5号及20号的 8:00, 8:30, 9:00, 9:30 执行
遇到复杂的情境,无法使用单一表示式来定义,可以考虑定义多个 Trigger 来触发相同 Job 。
var trigger = (ICronTrigger)TriggerBuilder.Create().WithIdentity("Main","Main") .WithCronSchedule("10,20,25 * * * * ? *").StartAt(DateTime.UtcNow).WithPriority(1).Build();
直接上加了Topshelf的代码,饭点吃饭去了,你品,你细细的品......
using log4net; using log4net.Repository; using Quartz; using Quartz.Impl; using System; using System.Threading; using System.Threading.Tasks; using Topshelf; namespace QuartzConsoleApp { internal class Program { private static ILoggerRepository _loggerRepository= LogManager.CreateRepository("rmb"); //static async Task Main(string[] args) static void Main(string[] args) { try { // 配置和运行宿主服务 HostFactory.Run(x => { x.UseLog4Net("App.config"); x.Service<ServiceRunner>(s => { // 指定服务类型。这里设置为 Service s.ConstructUsing(name => new ServiceRunner()); // 当服务启动后执行什么 s.WhenStarted((sc, hc) => sc.Start(hc)); // 当服务停止后执行什么 s.WhenStopped((sc, hc) => sc.Stop(hc)); }); // 服务用本地系统账号来运行 x.RunAsLocalSystem(); //x.StartAutomaticallyDelayed(); x.StartAutomatically(); // 服务描述信息 x.SetDescription("测试Greeting服务,此处是服务描述信息"); // 服务显示名称 x.SetDisplayName("测试Greeting服务"); // 服务名称 x.SetServiceName("GreetingJobService"); }); } catch (Exception ex) { Console.WriteLine(ex); } } } [DisallowConcurrentExecution] //禁止并发执行 public class GreetingJob : IJob { private readonly ILog _logger = LogManager.GetLogger(typeof(GreetingJob)); public Task Execute(IJobExecutionContext context) { //Console.WriteLine($"{DateTime.Now}:你好吗?----From GreetingJob"); _logger.Info($"{DateTime.Now}:你好吗?----From GreetingJob"); Thread.Sleep(3000); return Task.FromResult(true); } } public sealed class ServiceRunner : ServiceControl, ServiceSuspend { //调度器 private readonly IScheduler scheduler; public ServiceRunner() { scheduler = StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult(); //创建出一个具体的作业 var job = JobBuilder.Create<GreetingJob>().Build(); //配置一个触发器 var trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithSimpleSchedule(x => x.WithIntervalInSeconds(3).WithRepeatCount(int.MaxValue)).Build(); //加入作业调度池中 scheduler.ScheduleJob(job, trigger); } //开始 public bool Start(HostControl hostControl) { scheduler.Start(); return true; } //停止 public bool Stop(HostControl hostControl) { scheduler.Shutdown(false); return true; } //恢复所有 public bool Continue(HostControl hostControl) { scheduler.ResumeAll(); return true; } //暂停所有 public bool Pause(HostControl hostControl) { scheduler.PauseAll(); return true; } } }
配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration> <log4net> <appender name="ManagedColoredConsoleAppender" type="log4net.Appender.ManagedColoredConsoleAppender"> <mapping> <level value="ERROR" /> <foreColor value="Red" /> </mapping> <mapping> <level value="Info" /> <foreColor value="Green" /> </mapping> <mapping> <level value="DEBUG" /> <foreColor value="Blue" /> </mapping> <mapping> <level value="WARN" /> <foreColor value="Yellow" /> </mapping> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%d{ABSOLUTE} [%thread] %-5p %c{1}:%L - %m%n" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="DEBUG" /> <param name="LevelMax" value="Fatal" /> </filter> </appender> <appender name="RollingFile" type="log4net.Appender.RollingFileAppender"> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <file value=".\logs\" /> <datePattern value="'GreetingJobService_'yyyy.MM.dd'.log'" /> <staticLogFileName value="false" /> <appendToFile value="true" /> <rollingStyle value="Composite" /> <maxSizeRollBackups value="10" /> <maximumFileSize value="5MB" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%d [%t] %-5p %c - %m%n" /> </layout> </appender> <root> <level value="all" /> <appender-ref ref="ManagedColoredConsoleAppender" /> <appender-ref ref="RollingFile" /> </root> </log4net> </configuration>
调试效果
部署、开始、卸载服务只需要一句命令行就可以:
安装:你的程序.exe install 启动:你的程序.exe start 卸载:你的程序.exe uninstall
更多命令:你的程序.exe help