上一篇说了如何使用 Topshelf 组件快速创建 Windows 服务, 接下来介绍如何使用 Quartz.NET
关于 Quartz.NET 的好处, 网上搜索都是一大把一大把的, 我就不再多介绍.
先介绍需要用到的插件:
Quartz 版本我用的 2.6.2 的, 没有用 3.0 以上的, 因为你用了就会知道, 会打印出一大堆坑爹的日志文件,
我是没有找到如何屏蔽的办法, 如果你们谁有, 欢迎分享出来, 我也学习一下, 哈哈.
整个项目结构如下:
AppConfigHelper 文件需要改动一下, 增加如下属性
- /// <summary>
- /// 程序标识
- /// </summary>
- [ConfigurationProperty("AppKey", IsRequired = true)]
- public string AppKey
- {
- get { return base["AppKey"].ToString(); }
- internal set { base["AppKey"] = value; }
- }
- /// <summary>
- /// 程序集信息
- /// </summary>
- [ConfigurationProperty("TypeInfo", IsRequired = true)]
- public string TypeInfo
- {
- get { return base["TypeInfo"].ToString(); }
- internal set { base["TypeInfo"] = value; }
- }
AppConfig 文件也做稍微改动
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <!-- 该节点一定要放在最上边 -->
- <configSections>
- <section name="AppConfigHelper" type="Quartz.WinService.AppConfigHelper,Quartz.WinService"/>
- </configSections>
- <!--TopSelf 服务配置文件 -->
- <AppConfigHelper
- ServiceName="ProcessPrintLogService"
- Desc="日志打印服务"
- AppKey="ProcessPrintLogService"
- TypeInfo="ProcessService.ProcessPrintLogService,ProcessService"
- />
- <startup>
- <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
- </startup>
- </configuration>
ProcessPrintLogService 就是 Windows 服务要执行的逻辑程序文件, 可以执行任何你想要的功能
ProcessService.ProcessPrintLogService,ProcessService 是 命名空间. 类名, 类名 的格式, 用于后边反射程序集用
假如你要执行其他业务逻辑程序, 只需要更换这里的配置就行,
ProcessPrintLogService 业务逻辑内容如下: 这就是我们要执行的业务逻辑, 定时打印一段日志内容, 可以创建一个类库, 里边专门存放你要执行的业务逻辑
- namespace ProcessService
- {
- /// <summary>
- /// 日志打印服务
- /// </summary>
- public class ProcessPrintLogService
- {
- private Logger log = LogManager.GetCurrentClassLogger();
- /// <summary>
- /// 服务入口
- /// </summary>
- public void DoWork()
- {
- //log.Info("****************** 排行榜服务开始执行 ******************");
- try
- {
- PrintLogMethod();
- }
- catch (Exception ex)
- {
- log.Error(string.Format("排行榜服务异常, 原因:{0}", ex));
- }
- finally
- {
- //log.Info("****************** 排行榜服务结束执行 ******************");
- }
- }
- private void PrintLogMethod()
- {
- log.Trace(string.Format("我是日志:{0}号", Thread.CurrentThread.ManagedThreadId));
- }
- }
- }
然后需要新增加两个文件: quartz.config 和 quartz_jobs.xml
quartz.config 文件内容如下:
- # You can configure your scheduler in either <quartz> configuration section
- # or in quartz properties file
- # Configuration section has precedence
- quartz.scheduler.instanceName = ServiceQuartzScheduler
- # configure thread pool info
- quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
- quartz.threadPool.threadCount = 10
- quartz.threadPool.threadPriority = Normal
- # job initialization plugin handles our xml reading, without it defaults are used
- quartz.plugin.xml.type = Quartz.Plugin.xml.XMLSchedulingDataProcessorPlugin, Quartz
- quartz.plugin.xml.fileNames = ~/quartz_jobs.xml
- # 3.0 以上用以下配置
- # 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 = 555
- # quartz.scheduler.exporter.bindName = QuartzScheduler
- # quartz.scheduler.exporter.channelType = tcp
- # quartz.scheduler.exporter.channelName = httpQuartz
quartz.scheduler.instanceName = ServiceQuartzScheduler 是调度的实例名称, 可以随意自定义命名
其他的都是固定的, 不需要修改
quartz_jobs.xml 文件内容如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <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>ProcessPrintLogService</name>
- <group>ProcessPrintLogServiceGroup</group>
- <description > 日志打印服务</description>
- <job-type>Quartz.WinService.QuartzWork,Quartz.WinService</job-type>
- <durable>true</durable>
- <recover>false</recover>
- </job>
- <trigger>
- <cron>
- <name>ProcessPrintLogServiceTrigger</name>
- <group>ProcessPrintLogServiceTriggerGroup</group>
- <job-name>ProcessPrintLogService</job-name>
- <job-group>ProcessPrintLogServiceGroup</job-group>
- <misfire-instruction>SmartPolicy</misfire-instruction>
- <cron-expression>0/3 * * * * ? </cron-expression>
- </cron>
- </trigger>
- </schedule>
- </job-scheduling-data>
这个 xml 配置文件很重要! 需要重点说下
首先 job 节点 和 trigger 节点 都可以定义多个, 也就是一个服务可以跑多个不同的业务逻辑程序
先说 job 节点
name(必填) 任务名称, 多个 job 的 name 不能相同, 这里一般使用业务逻辑程序的名称就行了
group(选填) 任务所属分组, 用于标识任务所属分组, 一般用业务逻辑程序的名称 + Group 后缀 如:<group>sampleGroup</group>
description(选填) 任务描述, 用于描述任务具体内容, 如:<description > 打印日志服务</description>
job-type(必填) 任务类型, 任务的具体类型及所属程序集, 格式: 实现了 IJob 接口的包含完整命名空间的类名, 程序集名称, 如:<job-type>Quartz.Server.SampleJob, Quartz.Server</job-type>
durable(选填) 具体作用不知, 官方示例中默认为 true, 如:<durable>true</durable>
recover(选填) 具体作用不知, 官方示例中默认为 false, 如:<recover>false</recover>
- namespace Quartz.WinService
- {
- public class QuartzWork : IJob
- {
- private Logger log = LogManager.GetCurrentClassLogger();
- //ConcurrentDictionary 是线程安全的字典集
- private readonly ConcurrentDictionary<string, Lazy<Delegate>> _dynamicCache = new ConcurrentDictionary<string, Lazy<Delegate>>();
- // 记录当前工作接口是否已经工作
- private static readonly Dictionary<string, bool> WorkingNow = new Dictionary<string, bool>();
- /// <summary>
- /// 任务调度执行入口
- /// 实现 IJob 的 Execute 方法, 在 Execute 方法里编写要处理的业务逻辑, 系统就会按照 Quartz 的配置, 定时处理
- /// 当 Job 的 trigger 触发的时候, Execute(..) 方法就会在 scheduler 的工作线程中执行
- /// </summary>
- /// <param name="context"></param>
- public void Execute(IJobExecutionContext context)
- {
- try
- {
- Task.Factory.StartNew(() =>
- {
- var service = AppConfigHelper.Initity();
- WorkNow(service);
- });
- }
- catch (Exception ex)
- {
- log.Fatal($"执行 Quartz 调度异常, 信息:{ex.Message}");
- }
- //return Task.FromResult(true); // 返回一个 bool 类型的 Task, Quartz 3.0 版本以上需要用到
- }
- private void WorkNow(AppConfigHelper service)
- {
- string key = service.AppKey; //key 值
- lock (this)
- {
- if (!WorkingNow.ContainsKey(key))
- {
- WorkingNow.Add(key, false);
- }
- // 如果执行则跳出
- if (WorkingNow[key])
- {
- log.Trace($"服务 key:{key} 正在运行, 此次服务忽略");
- return;
- }
- // 并且设置为执行状态
- WorkingNow[key] = true;
- }
- try
- {
- var type = Type.GetType(service.TypeInfo); // 这里通过 App.config 文件设置
- if (type != null)
- {
- // 创建指定类型的实例, 相当于通过反射 new 了一个对象实例
- var provider = Activator.CreateInstance(type);
- Dynamic(provider, "DoWork", key);
- }
- else
- {
- log.Error($"任务:{key} 实例化失败");
- }
- }
- catch (Exception ex)
- {
- log.Fatal($"任务:{key} 实例化异常:{ex.Message}");
- }
- finally
- {
- WorkingNow[key] = false;
- }
- }
- //Delegate.CreateDelegate 官方定义: 用来动态创建指定类型的委托, 该委托可以对指定的类实例调用的指定的方法.
- // 简单来说: 就是可以调用指定类里边指定的方法, 前提是, 使用时需要实例化该类
- //GetOrAdd 函数会根据指定 key 判断是否存在对应内容, 存在则返回
- //DynamicInvoke 动态调用委托方法
- //obj 参数就是指定类的实例化对象, methodName 指定类中的方法名
- private void Dynamic(object obj, string methodName, string key)
- {
- var dmc = _dynamicCache.GetOrAdd(key, t => new Lazy<Delegate>(() => Delegate.CreateDelegate(typeof(Action), obj, methodName)));
- dmc.Value.DynamicInvoke(); // 动态调用委托方法
- }
- }
- }
- namespace Quartz.WinService
- {
- public class RegistService
- {
- /// <summary>
- /// 注册入口
- /// </summary>
- /// <param name="config">配置文件</param>
- /// <param name="isreg">是否注册</param>
- public static void Regist(AppConfigHelper config, bool isreg = false)
- {
- // 这里也可以使用 HostFactory.Run()代替 HostFactory.New()
- var host = HostFactory.New(x =>
- {
- x.Service<QuartzHost>(s =>
- {
- // 通过 new QuartzHost() 构建一个服务实例
- s.ConstructUsing(name => new QuartzHost());
- // 当服务启动后执行什么
- s.WhenStarted(tc => tc.Start());
- // 当服务停止后执行什么
- s.WhenStopped(tc => tc.Stop());
- // 当服务暂停后执行什么
- s.WhenPaused(w => w.Stop());
- // 当服务继续后执行什么
- s.WhenContinued(w => w.Start());
- });
- if (!isreg) return; //false 表示不注册
- // 服务用本地系统账号来运行
- x.RunAsLocalSystem();
- // 启用自动重启服务
- x.EnableServiceRecovery(v =>
- {
- v.RestartService(2); //2 分钟后重启
- });
- // 服务的描述信息
- x.SetDescription(config.Description);
- // 服务的显示名称
- x.SetDisplayName(config.ServiceName);
- // 服务的名称(最好不要包含空格或者有空格属性的字符)Windows 服务名称不能重复.
- x.SetServiceName(config.ServiceName);
- }).Run(); // 启动服务 如果使用 HostFactory.Run()则不需要该方法
- }
- }
- }
namespace Quartz.WinService { public class QuartzHost { private Logger log = LogManager.GetCurrentClassLogger(); private readonly IScheduler scheduler; public QuartzHost() { // 初始化调度服务 //scheduler = StdSchedulerFactory.GetDefaultScheduler().Result; //3.0 以上写法 scheduler = StdSchedulerFactory.GetDefaultScheduler(); } /// <summary> /// 调度开始 /// </summary> public void Start() { try { scheduler.Start(); log.Info("Quartz 调度服务开始工作"); } catch (Exception ex) { log.Fatal(string.Format("Quartz 调度服务开始异常! 错误信息:{0}", ex)); throw; } } /// <summary> /// 调度停止 /// </summary> public void Stop() { try { if (scheduler != null) { scheduler.Shutdown(true); } log.Info("Quartz 调度服务结束工作"); } catch (Exception ex) { log.Fatal(string.Format("Quartz 调度服务停止异常! 错误信息:{0}", ex)); throw; } } } }
https://blog.csdn.net/clb929/article/details/90341485 https://blog.csdn.net/weixin_33948416/article/details/92989386 https://www.cnblogs.com/lzrabbit/archive/2012/04/14/2446942.html
来源: https://www.cnblogs.com/peterzhang123/p/11908742.html