一,新建控制台應用程序
二,選中項目,右鍵 — 管理 NuGet 程序包,添加四個:
Quartz
Quartz.Plugins
Topshelf
log4net
三,創建項目文件
三個配置文件:必須放在項目根目錄下。
(1)log4net.config

<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> </configSections> <log4net> <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <!--日志路徑--> <param name= "File" value= "Logs/"/> <!--是否是向文件中追加日志--> <param name= "AppendToFile" value= "true"/> <!--log保留天數--> <param name= "MaxSizeRollBackups" value= "10"/> <!--日志文件名是否是固定不變的--> <param name= "StaticLogFileName" value= "false"/> <!--日志文件名格式為:yyyy-MM-dd.log--> <param name= "DatePattern" value= "yyyy-MM-dd".log""/> <!--日志根據日期滾動--> <param name= "RollingStyle" value= "Date"/> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss,fff}] [%p] [%c] %m%n %n" /> </layout> </appender> <!-- 控制台前台顯示日志 --> <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender"> <mapping> <level value="ERROR" /> <foreColor value="Red, HighIntensity" /> </mapping> <mapping> <level value="Info" /> <foreColor value="Green" /> </mapping> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%n%date{HH:mm:ss,fff} [%-5level] %m" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="Info" /> <param name="LevelMax" value="Fatal" /> </filter> </appender> <root> <!--(高) OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL (低) --> <level value="all" /> <appender-ref ref="ColoredConsoleAppender"/> <appender-ref ref="RollingLogFileAppender"/> </root> </log4net> </configuration>
(2)quartz.config

<?xml version="1.0" encoding="utf-8" ?> <!-- This file contains job definitions in schema version 2.0 format --> <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"> <processing-directives> <overwrite-existing-data>true</overwrite-existing-data> </processing-directives> <schedule> <job> <name>SampleJob</name> <group>SampleGroup</group> <description>Sample job for Quartz Server</description> <job-type>QuartzTopshelf.Jobs.SampleJob, QuartzTopshelf</job-type> <durable>true</durable> <recover>false</recover> <job-data-map> <entry> <key>key1</key> <value>value1</value> </entry> <entry> <key>key2</key> <value>value2</value> </entry> </job-data-map> </job> <trigger> <simple> <name>SampleSimpleTrigger</name> <group>SampleSimpleGroup</group> <description>Simple trigger to simply fire sample job</description> <job-name>SampleJob</job-name> <job-group>SampleGroup</job-group> <misfire-instruction>SmartPolicy</misfire-instruction> <repeat-count>-1</repeat-count> <repeat-interval>10000</repeat-interval> </simple> </trigger> <trigger> <cron> <name>SampleCronTrigger</name> <group>SampleCronGroup</group> <description>Cron trigger to simply fire sample job</description> <job-name>SampleJob</job-name> <job-group>SampleGroup</job-group> <misfire-instruction>SmartPolicy</misfire-instruction> <cron-expression>0/10 * * * * ?</cron-expression> </cron> </trigger> <trigger> <calendar-interval> <name>SampleCalendarIntervalTrigger</name> <group>SampleCalendarIntervalGroup</group> <description>Calendar interval trigger to simply fire sample job</description> <job-name>SampleJob</job-name> <job-group>SampleGroup</job-group> <misfire-instruction>SmartPolicy</misfire-instruction> <repeat-interval>15</repeat-interval> <repeat-interval-unit>Second</repeat-interval-unit> </calendar-interval> </trigger> </schedule> </job-scheduling-data>
也可以在項目配置文件App.config中配置,就不需要配置quartz.config
App.config

<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections> <quartz> <add key="quartz.scheduler.instanceName" value="ServerScheduler" /> <add key="quartz.threadPool.type" value="Quartz.Simpl.DefaultThreadPool, Quartz" /> <add key="quartz.threadPool.maxConcurrency" value="10" /> <add key="quartz.plugin.xml.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins" /> <add key="quartz.plugin.xml.fileNames" value="~/quartz_jobs.xml" /> <add key="quartz.scheduler.exporter.type" value="Quartz.Simpl.RemotingSchedulerExporter, Quartz" /> <add key="quartz.scheduler.exporter.port" value="555" /> <add key="quartz.scheduler.exporter.bindName" value="QuartzScheduler" /> <add key="quartz.scheduler.exporter.channelType" value="tcp" /> <add key="quartz.scheduler.exporter.channelName" value="httpQuartz" /> </quartz> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
(3)quartz_jobs.xml 具體我就不解釋了,不懂的可以去查下Quartz.NET

<?xml version="1.0" encoding="utf-8" ?> <!-- This file contains job definitions in schema version 2.0 format --> <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"> <processing-directives> <overwrite-existing-data>true</overwrite-existing-data> </processing-directives> <schedule> <job> <name>SampleJob</name> <group>SampleGroup</group> <description>Sample job for Quartz Server</description> <job-type>Quartz.Server.Jobs.SampleJob, Quartz.Server</job-type> <durable>true</durable> <recover>false</recover> <job-data-map> <entry> <key>key1</key> <value>value1</value> </entry> <entry> <key>key2</key> <value>value2</value> </entry> </job-data-map> </job> <trigger> <simple> <name>SampleSimpleTrigger</name> <group>SampleSimpleGroup</group> <description>Simple trigger to simply fire sample job</description> <job-name>SampleJob</job-name> <job-group>SampleGroup</job-group> <misfire-instruction>SmartPolicy</misfire-instruction> <repeat-count>-1</repeat-count> <repeat-interval>10000</repeat-interval> </simple> </trigger> <trigger> <cron> <name>SampleCronTrigger</name> <group>SampleCronGroup</group> <description>Cron trigger to simply fire sample job</description> <job-name>SampleJob</job-name> <job-group>SampleGroup</job-group> <misfire-instruction>SmartPolicy</misfire-instruction> <cron-expression>0/10 * * * * ?</cron-expression> </cron> </trigger> <trigger> <calendar-interval> <name>SampleCalendarIntervalTrigger</name> <group>SampleCalendarIntervalGroup</group> <description>Calendar interval trigger to simply fire sample job</description> <job-name>SampleJob</job-name> <job-group>SampleGroup</job-group> <misfire-instruction>SmartPolicy</misfire-instruction> <repeat-interval>15</repeat-interval> <repeat-interval-unit>Second</repeat-interval-unit> </calendar-interval> </trigger> </schedule> </job-scheduling-data>
三個類文件:
(1)Configuration.cs 配置類

using System.Collections.Specialized; using System.Configuration; namespace QuartzTopshelf { /// <summary> /// 定時服務的配置 /// </summary> public class Configuration { private const string PrefixServerConfiguration = "quartz.server"; private const string KeyServiceName = PrefixServerConfiguration + ".serviceName"; private const string KeyServiceDisplayName = PrefixServerConfiguration + ".serviceDisplayName"; private const string KeyServiceDescription = PrefixServerConfiguration + ".serviceDescription"; private const string KeyServerImplementationType = PrefixServerConfiguration + ".type"; private const string DefaultServiceName = "QuartzServer"; private const string DefaultServiceDisplayName = "Quartz Server"; private const string DefaultServiceDescription = "Quartz Job Scheduling Server"; private static readonly string DefaultServerImplementationType = typeof(QuartzServer).AssemblyQualifiedName; private static readonly NameValueCollection configuration; /// <summary> /// 初始化 Configuration 類 /// </summary> static Configuration() { configuration = (NameValueCollection)ConfigurationManager.GetSection("quartz"); } /// <summary> /// 獲取服務的名稱 /// </summary> /// <value>服務的名稱</value> public static string ServiceName { get { return GetConfigurationOrDefault(KeyServiceName, DefaultServiceName); } } /// <summary> /// 獲取服務的顯示名稱. /// </summary> /// <value>服務的顯示名稱</value> public static string ServiceDisplayName { get { return GetConfigurationOrDefault(KeyServiceDisplayName, DefaultServiceDisplayName); } } /// <summary> /// 獲取服務描述 /// </summary> /// <value>服務描述</value> public static string ServiceDescription { get { return GetConfigurationOrDefault(KeyServiceDescription, DefaultServiceDescription); } } /// <summary> /// 獲取服務器實現的類型名稱 /// </summary> /// <value>服務器實現的類型</value> public static string ServerImplementationType { get { return GetConfigurationOrDefault(KeyServerImplementationType, DefaultServerImplementationType); } } /// <summary> /// 返回具有給定鍵的配置值。如果的配置不存在,則返回默認值。 /// </summary> /// <param name="configurationKey">用於讀取配置的鍵</param> /// <param name="defaultValue">未找到配置時返回的默認值</param> /// <returns>配置值</returns> private static string GetConfigurationOrDefault(string configurationKey, string defaultValue) { string retValue = null; if (configuration != null) { retValue = configuration[configurationKey]; } if (retValue == null || retValue.Trim().Length == 0) { retValue = defaultValue; } return retValue; } } }
(2)QuartzServer.cs 定時服務類

using System; using System.Threading.Tasks; using log4net; using Quartz; using Quartz.Impl; using Topshelf; namespace QuartzTopshelf { /// <summary> /// Service interface for core Quartz.NET server. /// </summary> public interface IQuartzServer { /// <summary> /// Initializes the instance of <see cref="IQuartzServer"/>. /// Initialization will only be called once in server's lifetime. /// </summary> Task Initialize(); /// <summary> /// Starts this instance. /// </summary> void Start(); /// <summary> /// Stops this instance. /// </summary> void Stop(); /// <summary> /// Pauses all activity in scheduler. /// </summary> void Pause(); /// <summary> /// Resumes all activity in server. /// </summary> void Resume(); } /// <summary> /// The main server logic. /// </summary> public class QuartzServer : ServiceControl, IQuartzServer { private readonly ILog logger; private ISchedulerFactory schedulerFactory; private IScheduler scheduler; /// <summary> /// Initializes a new instance of the <see cref="QuartzServer"/> class. /// </summary> public QuartzServer() { logger = LogManager.GetLogger(GetType()); } /// <summary> /// Initializes the instance of the <see cref="QuartzServer"/> class. /// </summary> public virtual async Task Initialize() { try { schedulerFactory = CreateSchedulerFactory(); scheduler = await GetScheduler().ConfigureAwait(false); } catch (Exception e) { logger.Error("Server initialization failed:" + e.Message, e); } } /// <summary> /// Gets the scheduler with which this server should operate with. /// </summary> /// <returns></returns> protected virtual Task<IScheduler> GetScheduler() { return schedulerFactory.GetScheduler(); } /// <summary> /// Returns the current scheduler instance (usually created in <see cref="Initialize" /> /// using the <see cref="GetScheduler" /> method). /// </summary> protected virtual IScheduler Scheduler => scheduler; /// <summary> /// Creates the scheduler factory that will be the factory /// for all schedulers on this instance. /// </summary> /// <returns></returns> protected virtual ISchedulerFactory CreateSchedulerFactory() { return new StdSchedulerFactory(); } /// <summary> /// Starts this instance, delegates to scheduler. /// </summary> public virtual void Start() { try { scheduler.Start(); } catch (Exception ex) { logger.Fatal(string.Format("Scheduler start failed: {0}", ex.Message), ex); throw; } //logger.Info("Scheduler started successfully"); } /// <summary> /// Stops this instance, delegates to scheduler. /// </summary> public virtual void Stop() { try { scheduler.Shutdown(true); } catch (Exception ex) { logger.Error(string.Format("Scheduler stop failed: {0}", ex.Message), ex); throw; } //logger.Info("Scheduler shutdown complete"); } /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public virtual void Dispose() { // no-op for now } /// <summary> /// Pauses all activity in scheduler. /// </summary> public virtual void Pause() { scheduler.PauseAll(); } /// <summary> /// Resumes all activity in server. /// </summary> public void Resume() { scheduler.ResumeAll(); } /// <summary> /// TopShelf's method delegated to <see cref="Start()"/>. /// </summary> public bool Start(HostControl hostControl) { Start(); return true; } /// <summary> /// TopShelf's method delegated to <see cref="Stop()"/>. /// </summary> public bool Stop(HostControl hostControl) { Stop(); return true; } /// <summary> /// TopShelf's method delegated to <see cref="Pause()"/>. /// </summary> public bool Pause(HostControl hostControl) { Pause(); return true; } /// <summary> /// TopShelf's method delegated to <see cref="Resume()"/>. /// </summary> public bool Continue(HostControl hostControl) { Resume(); return true; } } }
(3)QuartzServerFactory.cs 工廠類

using System; using System.Reflection; using log4net; namespace QuartzTopshelf { /// <summary> /// 用於創建Quartz服務實現的工廠類. /// </summary> public class QuartzServerFactory { private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// <summary> /// 創建Quartz.NET服務核心的新實例 /// </summary> /// <returns></returns> public static QuartzServer CreateServer() { string typeName = Configuration.ServerImplementationType; Type t = Type.GetType(typeName, true); //logger.Debug("正在創建服務器類型的新實例'" + typeName + "'"); QuartzServer retValue = (QuartzServer)Activator.CreateInstance(t); //logger.Debug("已成功創建實例"); return retValue; } } }
服務啟動類:Program.cs

using System.IO; using System.Reflection; using Topshelf; namespace QuartzTopshelf { public static class Program { static void Main(string[] args) { // 從服務帳戶的目錄更改為更符合邏輯的目錄 Directory.SetCurrentDirectory(System.AppDomain.CurrentDomain.BaseDirectory); var logRepository = log4net.LogManager.GetRepository(Assembly.GetEntryAssembly()); log4net.Config.XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config")); HostFactory.Run(x => { x.RunAsLocalSystem(); x.SetDescription(Configuration.ServiceDescription); x.SetDisplayName(Configuration.ServiceDisplayName); x.SetServiceName(Configuration.ServiceName); x.Service(factory => { QuartzServer server = QuartzServerFactory.CreateServer(); server.Initialize().GetAwaiter().GetResult(); return server; }); }); } } }
四,添加Job類,以及在quartz_jobs.xml 配置job作業觸發規則
SampleJob.cs

using System; using System.Collections; using System.Reflection; using System.Threading.Tasks; using log4net; using Quartz; namespace QuartzTopshelf.Jobs { /// <summary> /// A sample job that just prints info on console for demostration purposes. /// </summary> public sealed class SampleJob : IJob { private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// <summary> /// Called by the <see cref="IScheduler" /> when a <see cref="ITrigger" /> /// fires that is associated with the <see cref="IJob" />. /// </summary> /// <remarks> /// The implementation may wish to set a result object on the /// JobExecutionContext before this method exits. The result itself /// is meaningless to Quartz, but may be informative to /// <see cref="IJobListener" />s or /// <see cref="ITriggerListener" />s that are watching the job's /// execution. /// </remarks> /// <param name="context">The execution context.</param> public async Task Execute(IJobExecutionContext context) { //通過配置文件傳遞參數 JobDataMap dataMap = context.JobDetail.JobDataMap; string key1 = dataMap.GetString("key1"); logger.Info("key1 : " + key1); string key2 = dataMap.GetString("key2"); logger.Info("key2 : " + key2); logger.Info("SampleJob running..."); //Thread.Sleep(TimeSpan.FromSeconds(5)); await Console.Out.WriteLineAsync("SampleJob is executing."); logger.Info("SampleJob run finished."); } } }
作業觸發規則在上面quartz_jobs.xml文件中配置
這里分析下特別提到下,job的trigger有以下幾種常用的方法:
-
SimpleTrigger:簡單的觸發器(重點)
-
CalendarIntervalTrigger:日歷觸發器(可自行研究)
-
CronTrigger:Cron表達式觸發器 (重點)
項目結構圖:
另外可以通過配置文件設置參數傳遞給作業:
相關源代碼地址:https://gitee.com/wyft/QuartzTopshelf