之前做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