quartz3.0.7和topshelf4.2.1实现任务调度
Posted bmyblogs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了quartz3.0.7和topshelf4.2.1实现任务调度相关的知识,希望对你有一定的参考价值。
我们都知道quartz是实现任务调度的,也就是定时计划,topshelf是创建windows服务。网上看过很多的例子,都不够完整,于是自己去翻了一遍quart.Net的源码,现在我就来根据quartz.net的源码例子,简单改造一下,基本上也就符合我们的工作使用了,同时quartz.Net还支持集群,可看文章末尾转至quartz.net进行参考。
一、创建一个控制台程序
二、引入相关插件
引入以下插件,可以通过管理NuGet程序包添加,我是通过程序包控制台添加的,如下:
PM> install-package quartz (安装这个时候同时会安装: quartz.job)
PM> install-package quartz.plugins
PM> install-package topshelf
我们还会使用到日志相关的,原官方的例子是用log4net, 我这里改了一下就用serilog
PM> install-package serilog
PM> install-package serilog.sinks.file (写入日志到文件)
PM> install-package serilog.sinks.console (输出日志到控制台)
三、复制相关文件到新建的项目中
1. Configuration.cs
2. IQuartzServer.cs
3. QuartzServer.cs
4. QuartzServerFactory.cs
5. quartz.config
6. quartz_jobs.xml
7. SampleJob.cs (实现了IJob接口)
8. LoggerProvider.cs (serilog日志相关)
9. Test.Production.config (单独的配置文件)
四、代码部分
/// <summary> /// Configuration for the Quartz server. /// </summary> public class Configuration { //private static readonly ILog log = LogManager.GetLogger(typeof(Configuration)); private const string PrefixServerConfiguration = "QuartzNetWindowsService"; 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 = "TestService"; private const string DefaultServiceDisplayName = "TestService"; private const string DefaultServiceDescription = "Quartz Job Scheduling Server"; private static readonly string DefaultServerImplementationType = typeof(QuartzServer).AssemblyQualifiedName; private static readonly NameValueCollection configuration; /// <summary> /// Initializes the <see cref="Configuration"/> class. /// </summary> static Configuration() { try { configuration = (NameValueCollection) ConfigurationManager.GetSection("quartz"); } catch (Exception e) { //log.Warn("could not read configuration using ConfigurationManager.GetSection: " + e.Message); } } /// <summary> /// Gets the name of the service. /// </summary> /// <value>The name of the service.</value> public static string ServiceName => GetConfigurationOrDefault(KeyServiceName, DefaultServiceName); /// <summary> /// Gets the display name of the service. /// </summary> /// <value>The display name of the service.</value> public static string ServiceDisplayName => GetConfigurationOrDefault(KeyServiceDisplayName, DefaultServiceDisplayName); /// <summary> /// Gets the service description. /// </summary> /// <value>The service description.</value> public static string ServiceDescription => GetConfigurationOrDefault(KeyServiceDescription, DefaultServiceDescription); /// <summary> /// Gets the type name of the server implementation. /// </summary> /// <value>The type of the server implementation.</value> public static string ServerImplementationType => GetConfigurationOrDefault(KeyServerImplementationType, DefaultServerImplementationType); /// <summary> /// Returns configuration value with given key. If configuration /// for the does not exists, return the default value. /// </summary> /// <param name="configurationKey">Key to read configuration with.</param> /// <param name="defaultValue">Default value to return if configuration is not found</param> /// <returns>The configuration value.</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; } } }
/// <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); throw; } } /// <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(); //// define the job and tie it to our HelloJob class //IJobDetail job = JobBuilder.Create<HelloJob>() // .WithIdentity("job1", "group1") // .Build(); } catch (Exception ex) { // logger.Fatal($"Scheduler start failed: {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($"Scheduler stop failed: {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; } } }
/// <summary> /// Factory class to create Quartz server implementations from. /// </summary> public class QuartzServerFactory { // private static readonly ILog logger = LogManager.GetLogger(typeof (QuartzServerFactory)); /// <summary> /// Creates a new instance of an Quartz.NET server core. /// </summary> /// <returns></returns> public static QuartzServer CreateServer() { string typeName = Configuration.ServerImplementationType; Type t = Type.GetType(typeName, true); // logger.Debug("Creating new instance of server type ‘" + typeName + "‘"); QuartzServer retValue = (QuartzServer) Activator.CreateInstance(t); // logger.Debug("Instance successfully created"); return retValue; } } }
# You can configure your scheduler in either <quartz> configuration section # or in quartz properties file # Configuration section has precedence quartz.scheduler.instanceName = ServerScheduler # configure thread pool info quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz quartz.threadPool.threadCount = 60 # job initialization plugin handles our xml reading, without it defaults are used quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins quartz.plugin.xml.fileNames = ~/quartz_jobs.xml # export this server to remoting context quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz quartz.scheduler.exporter.port = 21004 quartz.scheduler.exporter.bindName = QuartzScheduler quartz.scheduler.exporter.channelType = tcp quartz.scheduler.exporter.channelName = httpQuartz
<?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> <!--ProductTaskJob--> <job> <name>ProductTaskJob</name> <group>ProductTaskJobGroup</group> <description>ProductTaskJob for Quartz Server</description> <job-type>QuartzNetWindowsService.Order.ProductTaskJob, QuartzNetWindowsService</job-type> <durable>true</durable> <recover>false</recover> </job> <trigger> <cron> <name>ProductTaskJobTrigger</name> <group>ProductTaskJobSimpleGroup</group> <description>ProductTaskJob trigger List</description> <job-name>ProductTaskJob</job-name> <job-group>ProductTaskJobGroup</job-group> <cron-expression>0/5 * * * * ?</cron-expression> </cron> </trigger> <!--ListJob--> <job> <name>ListJob</name> <group>ListJobGroup</group> <description>ListJob for Quartz Server</description> <job-type>QuartzNetWindowsService.Order.ListJob, QuartzNetWindowsService</job-type> <durable>true</durable> <recover>false</recover> </job> <trigger> <cron> <name>ListJobTrigger</name> <group>ListJobSimpleGroup</group> <description>ListJob trigger List</description> <job-name>ListJob</job-name> <job-group>ListJobGroup</job-group> <cron-expression>0/5 * * * * ?</cron-expression> </cron> </trigger> <job> <name>sampleJob</name> <group>sampleGroup</group> <description>Sample job for Quartz Server</description> <job-type>QuartzNetWindowsService.SampleJob, QuartzNetWindowsService</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>
/// <summary> /// A sample job that just prints info on console for demostration purposes. /// </summary> public class SampleJob : IJob { /// <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) { LoggerProvider.LogInfomation(nameof(SampleJob) + "正在执行中。。。。。。。。。。。。"); await Task.Delay(1); } } }
class Constants { public const string logPath = "serilog:write-to:File.path"; public const string logLimitFileSize = "serilog:write-to:File.fileSizeLimitBytes"; } /// <summary> /// Serilog日志 /// </summary> public class LoggerProvider { private readonly static ILogger log; static LoggerProvider() { log = new LoggerConfiguration() .WriteTo.File( path: ConfigurationManager.AppSettings[Constants.logPath], rollingInterval: RollingInterval.Day, fileSizeLimitBytes: Convert.ToInt32(ConfigurationManager.AppSettings[Constants.logLimitFileSize]), rollOnFileSizeLimit: true ).CreateLogger(); Console.WriteLine(1122); } public static void LogInfomation(string message, Exception exception = null) { log.Information(exception, message); } public static void LogDebug(string message, Exception exception = null) { log.Debug(exception, message); } public static void LogWarning(string message, Exception exception = null) { log.Warning(exception, message); } public static void LogError(string message, Exception exception = null) { log.Error(exception, message); } } }
这里设置日志输出的路径和设置日志的文件的大小 <?xml version="1.0" encoding="utf-8" ?> <appSettings> <add key="serilog:write-to:File.path" value="Logs est-.txt" /> <add key="serilog:write-to:File.fileSizeLimitBytes" value="1234567" /> </appSettings>
五、修改控制台程序入口
static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> static void Main() { Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); 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; }); }); } } }
六、修改配置文件为始终复制
七、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>
<startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> </startup> <appSettings configSource="Test.Production.config"></appSettings> </configuration>
八、新建一个类库,创建一个产品业务相关类:ProductAppService
然后 QuartzNetWindowsService 引用 类库 ProductAppService,并且新建一个ProductTaskJob:IJob ,实现IJob接口
public class ProductAppService { public string GetProductList() { return nameof(ProductAppService) + ":我获取到产品列表啦!"; } }
public class ProductTaskJob : IJob { public async Task Execute(IJobExecutionContext context) { //具体业务实现 var result = new BMYAppService.ProductAppService().GetProductList(); //输出日志 LoggerProvider.LogInfomation(result); await Task.Delay(1); } } }
九、把控制台安装到电脑中变成服务
新建一个文本文件,命名为:安装服务.bat ,注意:另存为时候,编码格式选择为:ANSI ,保存后再修改后缀名为*.bat,卸载服务也是一样的,这里路径可以自己设置,我为了方便直接复制程序运行路径
看下我的安装结果,并查看相关日志记录,证明已经在运行了!
至此,整个例子实现完成。如有疑问或错误请留言指正。谢谢!
quart.net 参考文献:https://www.quartz-scheduler.net/
job文件相关表达式执行配置参考:http://qqe2.com/cron
Demo下载:csdn还在审核。
以上是关于quartz3.0.7和topshelf4.2.1实现任务调度的主要内容,如果未能解决你的问题,请参考以下文章
第三十一节:扫盲并发和并行同步和异步进程和线程阻塞和非阻塞响应和吞吐等
shell中$()和 ` `${}${!}${#}$[] 和$(()),[ ] 和(( ))和 [[ ]]
Java基础8---面向对象代码块和继承和this和super和重写和重载和final
Java基础8---面向对象代码块和继承和this和super和重写和重载和final
JS中some()和every()和join()和concat()和pop(),push(),shift(),unshfit()和map()和filter()