1.TopShelf
TopShelf是一個開源的跨平台的宿主服務框架。可通過.Net Core/.Net Framwork控制台應用程序快速開發windows服務,更加便於服務調試。
本文基於.Net Core2.2快速開發windows服務
首先,我們創建一個控制台應用程序
然后添加Topshelf Nuget程序包 版本4.2.1
通過Topshelf集成的Log4net管理日志,所以我們這里添加了Topshelf.LogNet4 Nuget程序包
添加log4net.config日志配置文件(需手動新建config文件,復制以下內容即可),一般默認配置就可以,主要是改一下日志路徑和控制台日志輸出級別
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <configSections> 4 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> 5 </configSections> 6 <log4net> 7 <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> 8 <!--日志路徑--> 9 <param name= "File" value= "D:\log\"/> 10 <!--是否是向文件中追加日志--> 11 <param name= "AppendToFile" value= "true"/> 12 <!--備份文件的最大切分數量--> 13 <param name= "MaxSizeRollBackups" value= "100"/> 14 <!-- 每個文件的大小限制 --> 15 <param name="MaximumFileSize" value="10MB" /> 16 <!-- RollingStyle Composite 綜合 Size 按大小 Date 按時間 --> 17 <param name="RollingStyle" value="Composite" /> 18 <!--最小鎖定模式,允許多個進程寫入同一個文件--> 19 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> 20 <!--日志文件名是否是固定不變的--> 21 <param name= "StaticLogFileName" value= "false"/> 22 <!--日志文件名格式為:2008-08-31.log--> 23 <datePattern value="yyyy-MM-dd\\"Log4Net"'.log'" /> 24 <layout type="log4net.Layout.PatternLayout"> 25 <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" /> 26 </layout> 27 </appender> 28 <!-- 控制台前台顯示日志 --> 29 <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender"> 30 <mapping> 31 <level value="ERROR" /> 32 <foreColor value="Red, HighIntensity" /> 33 </mapping> 34 <mapping> 35 <level value="Info" /> 36 <foreColor value="Green" /> 37 </mapping> 38 <layout type="log4net.Layout.PatternLayout"> 39 <conversionPattern value="%d [%-5level] %m%n" /> 40 </layout> 41 42 <filter type="log4net.Filter.LevelRangeFilter"> 43 <param name="LevelMin" value="Info" /> 44 <param name="LevelMax" value="Fatal" /> 45 </filter> 46 </appender> 47 48 <root> 49 <!--(高) OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL (低) --> 50 <level value="INFO" /> 51 <appender-ref ref="ColoredConsoleAppender"/> 52 <appender-ref ref="RollingLogFileAppender"/> 53 </root> 54 </log4net> 55 </configuration>
Topshelf關鍵點:下面我們開始修改Program文件中程序主函數,如下
1 //使用Log4進行日志管理 2 static ILog log = LogManager.GetLogger(typeof(Program)); 3 public static void Main(string[] args) 4 { 5 try 6 { 7 //Console.WriteLine("Hello World!"); 8 HostFactory.Run(x => 9 { 10 x.UseLog4Net("log4net.config", true);//使用配置文件 11 //指定服務 12 x.Service<MyService>(y => 13 { 14 y.ConstructUsing<MyService>(service => new MyService());//實際業務邏輯處理的地方 15 y.WhenStarted((tc, th) => tc.Start(th));//自定義服務類啟動動作,其中th表示自定義服務類實現的ServiceControl接口方法中的入參 16 y.WhenStopped((ts, th) => ts.Stop(th));//自定義服務類結束動作 17 }); 18 19 x.RunAsLocalSystem(); 20 21 // 服務描述信息 22 x.SetDescription(ConfigurationManager.AppSettings["ServiceDescription"]); 23 // 服務顯示名稱 24 x.SetDisplayName(ConfigurationManager.AppSettings["ServiceDisplayName"]); 25 // 服務名稱 26 x.SetServiceName(ConfigurationManager.AppSettings["ServiceName"]); 27 }); 28 } 29 catch (Exception ex) 30 { 31 log.Error("服務異常:" + ex.Message); 32 } 33 }
其中服務的一些基本信息(服務名稱,描述信息等)我們通過App.config配置文件進行統一管理。添加App.config文件
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <appSettings> 4 <!--服務名稱--> 5 <add key="ServiceName" value="QuartzService"/> 6 <!--服務顯示名稱--> 7 <add key="ServiceDisplayName" value="Quartz"/> 8 <!--服務描述--> 9 <add key="ServiceDescription" value="Quartz定時服務"/> 10 <!--操作人--> 11 <add key="OpUser" value="Quartz"/> 12 </appSettings> 13 <connectionStrings> 14 <!--數據庫連接字符串>--> 15 </connectionStrings> 16 </configuration>
主函數中我們指定了MyService自定義功能類,它是實際處理業務邏輯的地方,也是程序功能處理的入口。自定義類實現了ServiceControl接口,該接口一共包含兩個需實現的方法:
bool Start(HostControl hostControl);
bool Stop(HostControl hostControl);
1 public class MyService: ServiceControl 2 { 3 /// <summary> 4 /// 程序開始入口 5 /// </summary> 6 /// <param name="hostControl"></param> 7 /// <returns></returns> 8 public bool Start(HostControl hostControl) 9 { 10 //do something you want to do here 11 return true; 12 } 13 14 /// <summary> 15 /// 程序結束 16 /// </summary> 17 /// <param name="hostControl"></param> 18 /// <returns></returns> 19 public bool Stop(HostControl hostControl) 20 { 21 return true; 22 } 23 }
至此,我們通過Topshelf快速開發windows服務的功能已基本完成,同學只需要在Start()方法中添加自己的服務處理功能即可。
但是,我們這里想介紹下如何結合Quart任務調度框架使用。
首先簡單介紹下Quartz:
Quartz 是一個開源作業調度框架,允許程序開發人員根據時間的間隔來調度作業,實現了作業和觸發器的多對多的關系,還能把多個作業與不同的觸發器關聯。
我們需要明白 Quartz 的幾個核心概念,這樣理解起 Quartz 的原理就會變得簡單了。
- Job 表示一個工作,要執行的具體內容。此接口中只有一個方法,如下:(Quartz3.0版本 方法返類型是Task,老版本為void)
Task Execute(IJobExecutionContext context);
- JobDetail 表示一個具體的可執行的調度程序,Job 是這個可執行程調度程序所要執行的內容,另外 JobDetail 還包含了這個任務調度的方案和策略。
- Trigger 代表一個調度參數的配置,什么時候去調。
- Scheduler 代表一個調度容器,一個調度容器中可以注冊多個 JobDetail 和 Trigger。當 Trigger 與 JobDetail 組合,就可以被 Scheduler 容器調度了。
好了,了解了上面幾個概念之后,我們准備開始入手。本文以配置文件的方式進行作業調度管理。
第一步,添加Quartz3.0 Nuget程序包
第二步,修改我們上面添加的自定義服務類,如下:
主要做如下調整:
1.聲明並初始化Quartz調度程序實例,其中scheduler初始化方式和老版本的有點不同,Quartz3.0版本的返回值是Task,我們通過方式1和方式2都可以實現(實際使用中,選擇一種即可)
2.程序開始和結束方法,調用scheduler的Start()和Shutdown()
1 private IScheduler scheduler;//聲明Quartz調度程序實例,用與管理Job 2 /// <summary> 3 /// 構造函數初始化IScheduler實例 4 /// </summary> 5 public MyService() 6 { 7 //Quartz3.0版本初始化方式1 8 scheduler = StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult(); 9 //Quartz3.0版本初始化方式2 10 //scheduler = StdSchedulerFactory.GetDefaultScheduler().Result; 11 } 12 13 /// <summary> 14 /// 程序開始入口 15 /// </summary> 16 /// <param name="hostControl"></param> 17 /// <returns></returns> 18 public bool Start(HostControl hostControl) 19 { 20 scheduler.Start(); 21 return true; 22 } 23 24 /// <summary> 25 /// 程序結束 26 /// </summary> 27 /// <param name="hostControl"></param> 28 /// <returns></returns> 29 public bool Stop(HostControl hostControl) 30 { 31 scheduler.Shutdown(); 32 return true; 33 }
第三步,我們需要添加工作任務job,這里是實際干活的任務。新建一個功能類,實現接口IJob。
1 /// <summary> 2 /// 自定義job,實際功能處理單元,需實現IJob 3 /// </summary> 4 public class TestJob : IJob 5 { 6 log4net.ILog _logger = log4net.LogManager.GetLogger(typeof(TestJob)); 7 public Task Execute(IJobExecutionContext context) 8 { 9 _logger.InfoFormat("TestJob測試"); 10 //return Task.FromResult("TestJob測試"); 11 return Task.Factory.StartNew(() => Console.WriteLine($"工作任務測試:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}")); 12 } 13 }
最后,也是比較重要的地方。我們需添加配置文件,來關聯我們上面創建的schedule和job。
添加配置文件有個注意的地方:文件屬性要選擇始終復制,便於發布版本時可用。
添加quartz.config
默認配置就可以,里面指定的調度程序實例,線程池數量,配置文件路徑名稱等
1 # You can configure your scheduler in either <quartz> configuration section 2 # or in quartz properties file 3 # Configuration section has precedence 4 5 quartz.scheduler.instanceName = ServerScheduler 6 7 # configure thread pool info 8 quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz 9 quartz.threadPool.threadCount = 50 10 11 # job initialization plugin handles our xml reading, without it defaults are used 12 quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins 13 quartz.plugin.xml.fileNames = ~/quartz_jobs.xml 14 15 # export this server to remoting context 16 quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz 17 quartz.scheduler.exporter.port = 555 18 quartz.scheduler.exporter.bindName = QuartzScheduler 19 quartz.scheduler.exporter.channelType = tcp 20 quartz.scheduler.exporter.channelName = httpQuartz
添加quartz_jobs.xml
該配置文件才是體現精華的地方。這里面真正實現了schedule,job和trigger三者的聯系。也是好多同學上面有疑問的地方(為什么schedule.start()之后會自動調用我創建的job)
配置注意點:
1.job->job-type,配置job類和job類所在的命名空間
2.trigger->job-name/job-group 一定要和你上面創建的job的name/group一樣
1 <?xml version="1.0" encoding="UTF-8"?> 2 3 <!-- This file contains job definitions in schema version 2.0 format --> 4 5 <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"> 6 7 <processing-directives> 8 <overwrite-existing-data>true</overwrite-existing-data> 9 </processing-directives> 10 11 <schedule> 12 13 <job> 14 <name>sampleJob</name> 15 <group>sampleGroup</group> 16 <description>Sample job for Quartz Server</description> 17 <job-type>QuartzServer.TestJob, QuartzServer</job-type> 18 <durable>true</durable> 19 <recover>false</recover> 20 <!--<job-data-map> 21 <entry> 22 <key>key1</key> 23 <value>value1</value> 24 </entry> 25 <entry> 26 <key>key2</key> 27 <value>value2</value> 28 </entry> 29 </job-data-map>--> 30 </job> 31 <!--當僅需觸發一次或者以固定時間間隔周期執行,SimpleTrigger是最適合的選擇--> 32 <!--<trigger> 33 <simple> 34 <name>sampleSimpleTrigger</name> 35 <group>sampleSimpleGroup</group> 36 <description>Simple trigger to simply fire sample job</description> 37 <job-name>sampleJob</job-name> 38 <job-group>sampleGroup</job-group> 39 <misfire-instruction>SmartPolicy</misfire-instruction> 40 <repeat-count>-1</repeat-count> 41 <repeat-interval>10000</repeat-interval> 42 </simple> 43 </trigger>--> 44 <!--通過Cron表達式定義出各種復雜時間規則的調度方案:如每早晨9:00執行,周一、周三、周五下午5:00執行等--> 45 <trigger> 46 <cron> 47 <name>sampleCronTrigger</name> 48 <group>sampleCronGroup</group> 49 <description>Cron trigger to simply fire sample job</description> 50 <job-name>sampleJob</job-name> 51 <job-group>sampleGroup</job-group> 52 <misfire-instruction>SmartPolicy</misfire-instruction> 53 <cron-expression>0/10 * * * * ?</cron-expression> 54 </cron> 55 </trigger> 56 <!--quartz.Calendar它是一些日歷特定時間點的集合,一個Trigger可以和多個Calendar關聯,以便排除或包含某些時間點。 57 假設,我們安排每周星期一早上10:00執行任務,但是如果碰到法定的節日,任務則不執行,這時就需要在Trigger觸發機制的基礎上使用Calendar進行定點排除--> 58 <!--<trigger> 59 <calendar-interval> 60 <name>sampleCalendarIntervalTrigger</name> 61 <group>sampleCalendarIntervalGroup</group> 62 <description>Calendar interval trigger to simply fire sample job</description> 63 <job-name>sampleJob</job-name> 64 <job-group>sampleGroup</job-group> 65 <misfire-instruction>SmartPolicy</misfire-instruction> 66 <repeat-interval>15</repeat-interval> 67 <repeat-interval-unit>Second</repeat-interval-unit> 68 </calendar-interval> 69 </trigger>--> 70 </schedule> 71 </job-scheduling-data>
常用的cron-trigger表達式配置說明
1 cron expressions 整體上還是非常容易理解的,只有一點需要注意:"?"號的用法,看下文可以知道“?”可以用在 day of month 和 day of week中,他主要是為了解決如下場景,如:每月的1號的每小時的31分鍾,正確的表達式是:* 31 * 1 * ?,而不能是:* 31 * 1 * *,因為這樣代表每周的任意一天。 2 3 由7段構成:秒 分 時 日 月 星期 年(可選) 4 "-" :表示范圍 MON-WED表示星期一到星期三 5 "," :表示列舉 MON,WEB表示星期一和星期三 6 "*" :表是“每”,每月,每天,每周,每年等 7 "/" :表示增量:0/15(處於分鍾段里面) 每15分鍾,在0分以后開始,3/20 每20分鍾,從3分鍾以后開始 8 "?" :只能出現在日,星期段里面,表示不指定具體的值 9 "L" :只能出現在日,星期段里面,是Last的縮寫,一個月的最后一天,一個星期的最后一天(星期六) 10 "W" :表示工作日,距離給定值最近的工作日 11 "#" :表示一個月的第幾個星期幾,例如:"6#3"表示每個月的第三個星期五(1=SUN...6=FRI,7=SAT) 12 13 官方實例 14 Expression Meaning 15 0 0 12 * * ? 每天中午12點觸發 16 0 15 10 ? * * 每天上午10:15觸發 17 0 15 10 * * ? 每天上午10:15觸發 18 0 15 10 * * ? * 每天上午10:15觸發 19 0 15 10 * * ? 2005 2005年的每天上午10:15觸發 20 0 * 14 * * ? 在每天下午2點到下午2:59期間的每1分鍾觸發 21 0 0/5 14 * * ? 在每天下午2點到下午2:55期間的每5分鍾觸發 22 0 0/5 14,18 * * ? 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鍾觸發 23 0 0-5 14 * * ? 在每天下午2點到下午2:05期間的每1分鍾觸發 24 0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44觸發 25 0 15 10 ? * MON-FRI 周一至周五的上午10:15觸發 26 0 15 10 15 * ? 每月15日上午10:15觸發 27 0 15 10 L * ? 每月最后一日的上午10:15觸發 28 0 15 10 L-2 * ? Fire at 10:15am on the 2nd-to-last last day of every month 29 0 15 10 ? * 6L 每月的最后一個星期五上午10:15觸發 30 0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month 31 0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一個星期五上午10:15觸發 32 0 15 10 ? * 6#3 每月的第三個星期五上午10:15觸發 33 0 0 12 1/5 * ? Fire at 12pm (noon) every 5 days every month, starting on the first day of the month. 34 0 11 11 11 11 ? Fire every November 11th at 11:11am.
好了,大功告成,我們只需要發布部署即可。

在服務列表中可看到我們剛才安裝的windows服務
好了,本文關於介紹topshelf框架快速開發windows服務以及通過Quartz框架調度管理服務的開發已介紹完畢,文中有不足之處,請各位看官多多指正!