使用Quartz.net实现多线程任务定时执行,动态配置Job,结合Topshelf构建Windows服务

article/2025/10/21 12:16:22

几个月前有这么个需求:需要执行一些Job,这些Job会各自按照不同的时间频次执行,且它们做的事情也不同,有的是监控站点,有的是监控服务器存储情况,有的是监控报表PROCEDURE的执行状况…

OK,当看到这么个需求,首先我想肯定是要用多线程来处理了,并且还要考虑到性能问题,领导当时给出一个方案,建议看一下Quartz.net,于是,研究了几天,看了不少资料,总算把监控引擎做出了一个版本,今天有时间,来整理一下大致过程,记录一下代码。

整个监控引擎,简单来说,一是使用Quartz.Net实现多线程执行任务,二是使用Topshelf构建Windows Server

一:Quartz.Net,官方:https://www.quartz-scheduler.net/

二:TopShelf,请自行度娘吧

工具:VS2019

首先创建一个控制台应用程序

然后NuGet引用Topshelf.dll程序集

添加控制台代码如下:

class Program{static void Main(string[] args){var rc = HostFactory.Run(x =>{x.Service<TownCrier>(s =>{s.ConstructUsing(name => new TownCrier());s.WhenStarted(tc => tc.Start());s.WhenStopped(tc => tc.Stop());});x.RunAsLocalSystem();x.SetDescription("服務说明");x.SetDisplayName("DisplayName自定义");x.SetServiceName("ServiceName自定义");});var exitCode = (int)Convert.ChangeType(rc, rc.GetTypeCode());Environment.ExitCode = exitCode;}}public class TownCrier{public void Start(){//开始执行}public void Stop(){//停止执行}}

以上是程序的入口,接下来才真正涉及到如何开展多线程任务,如何使用Quartz.net

先创建一个类库QuartzBLL,并安装Quartz引用:

这里直接提供工具类QuartzManage,看了很多网友的例子,但都没调通,然后自己摸索着整理出来一个工具类,或有不足之处,请指出,多谢!

	public class QuartzManage{static Task<IScheduler> task_scheduler = StdSchedulerFactory.GetDefaultScheduler();static IScheduler scheduler;private static readonly object objlock = new object();static QuartzManage(){if (scheduler == null){lock (objlock){if (scheduler == null){scheduler = task_scheduler.Result;scheduler.Start();}}}}/// <summary>/// 以Simple开始一个工作/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <param name="simpleInterval"></param>public static void StartJobWithSimple<T>(string name, Action<SimpleScheduleBuilder> simpleInterval) where T : IJob{IJobDetail job = JobBuilder.Create<T>().WithIdentity(name + "_job", name + "_group").Build();ITrigger Simple = TriggerBuilder.Create().StartNow().WithSimpleSchedule(simpleInterval).Build() as ISimpleTrigger;scheduler.ScheduleJob(job, Simple);}/// <summary>/// 以Cron开始一个工作/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <param name="CronExpression"></param>public static void StartJobWithCron<T>(string name, string CronExpression) where T : IJob{IJobDetail job = JobBuilder.Create<T>().WithIdentity(name + "_job", name + "_group").Build();ITrigger CronTrigger = TriggerBuilder.Create().StartNow().WithIdentity(name + "_trigger", name + "_group").WithCronSchedule(CronExpression, w => w.WithMisfireHandlingInstructionDoNothing()).Build() as ICronTrigger;scheduler.ScheduleJob(job, CronTrigger);}/// <summary>/// 這種辦法可以根據job名稱找到觸發器,也可以找到Job,這樣就可以在任何地方修改Job頻次,不再限於IJob的實現方法Execute內/// 以此實現了:即時修改執行頻次即時生效/// </summary>/// <param name="cronExpression"></param>/// <param name="name"></param>public static async void ModifyJob(string cronExpression, string name){//触发器的keyTriggerKey triggerKey = new TriggerKey(name + "_trigger", name + "_group");ITrigger trigger = await scheduler.GetTrigger(triggerKey);CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.CronSchedule(cronExpression);trigger = trigger.GetTriggerBuilder().WithIdentity(triggerKey).WithSchedule(scheduleBuilder).Build();await scheduler.RescheduleJob(triggerKey, trigger);}/// <summary>/// 删除Job,即時刪除,不再限於IJob的實現方法Execute內/// </summary>/// <param name="job"></param>/// <param name="trigger"></param>public static void DeleteJob(string name){//触发器的keyTriggerKey triggerKey = new TriggerKey(name + "_trigger", name + "_group");//Job的KeyJobKey jobKey = new JobKey(name + "_job", name + "_group");scheduler.PauseTrigger(triggerKey);//暫停觸發器scheduler.UnscheduleJob(triggerKey);//移除觸發器scheduler.DeleteJob(jobKey);}/// <summary>/// 删除Job/// </summary>/// <param name="job"></param>/// <param name="trigger"></param>public static void DeleteJob(IJobDetail job, ITrigger trigger){scheduler.PauseTrigger(trigger.Key);//暫停觸發器scheduler.UnscheduleJob(trigger.Key);//移除觸發器scheduler.DeleteJob(job.Key);}/// <summary>/// 停止運行/// </summary>public static void ShutDownJob(){if (scheduler != null && !scheduler.IsShutdown){scheduler.Shutdown();}}}

还有一种方式,是可以自定义调度器的属性:比如线程池数量threadCount…默认的数量好像是10吧,记不清楚了,也有人把配置文件放在xml文件中,程序里读取配置文件来创建调度器。具体怎么玩,视情况而定吧。

	public class QuartzHelper{private static IScheduler scheduler = null;static QuartzHelper(){var properties = new NameValueCollection();// 设置线程池properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";//设置线程池的最大並發线程数量properties["quartz.threadPool.threadCount"] = "5";//设置作业中每个线程的优先级properties["quartz.threadPool.threadPriority"] = ThreadPriority.Normal.ToString();远程输出配置//properties["quartz.scheduler.exporter.type"] = "Quartz.Simpl.RemotingSchedulerExporter, Quartz";//properties["quartz.scheduler.exporter.port"] = "555";  //配置端口号//properties["quartz.scheduler.exporter.bindName"] = "QuartzScheduler";//properties["quartz.scheduler.exporter.channelType"] = "tcp"; //协议类型if (scheduler == null){//创建一个工厂var schedulerFactory = new StdSchedulerFactory(properties);//启动scheduler = schedulerFactory.GetScheduler().Result;//1、开启调度scheduler.Start();}}}

  添加测试类

拿TestJob1举例,旧版的Quartz好像在实现IJob接口的时候,还不是异步方法,最新版本是异步,注意async await:

class TestJob1 : IJob{public async Task Execute(IJobExecutionContext context){await Task.Run(() =>{Console.WriteLine($"第一个Job执行了,Time:{DateTime.Now}");            });}}class TestJob2 : IJob{public async Task Execute(IJobExecutionContext context){await Task.Run(() =>{Console.WriteLine($"第二个Job执行了,Time:{DateTime.Now}");            });}}

然后定义一个JobStart类,作为程序业务入口类,将TestJob1和TestJob2注册在Quartz中,调度器会按照各自的频次(CronExpression)执行Job,可以这么理解:一个Job对应一个线程,如果所有Job的CronExpression一样,Job就会在同一时间同时执行,此时如果线程池中线程数量不够同时处理这些Job,则会排序等待,就像消息队列一样(线程池线程数量可以自定义)

	public class JobStart{public void Start(){QuartzManage.StartJobWithCron<TestJob1>("TestJob1", "0/5 * * * * ?");//5秒钟一次QuartzManage.StartJobWithCron<TestJob2>("TestJob2", "0/8 * * * * ?");//8秒钟一次Console.ReadLine();}}

最后,在Program,TownCrier的Start方法中启动程序。

看一下效果

OK,继续吧。

此处仅示例展示,如果是配置在数据库中的Job,需要动态加载?

我的做法是建一个工厂类JobFactory,來根據不同的任务设定的Type,來返回不同的TestJob类(如果Job类执行的任务大致相同,比如:你的系统中有很多执行计划的任务,其实就是不同的sql或者procedure,那么不需要建很多的TestJob类,只需要一个类即可)。

然後自定义一个接口(IJobMonitor),声明两个方法,StartJob和DeleteJob。

将TestJob类实现该接口IJobMonitor。

也可以单独建立一个类库文件,通过反射创建Job,当然要视情况而定,需不需要用反射?

		/// <summary>/// 通過反射加載/// </summary>/// <param name="category"></param>public static void CreatJob(Config_DataVerifyCategory category){//加载程序及路径Assembly ass = Assembly.LoadFrom(@"D:\xx\TestJobAssembly.dll");if (ass != null){Type[] types = ass.GetExportedTypes();foreach (Type t in types){if (typeof(IJob).IsAssignableFrom(t)){IJobDetail jobDetail = JobBuilder.Create(t).WithIdentity(category.Code + "_CaseHandingJob", category.Code + "_CaseHandingJobGroup").Build();jobDetail.JobDataMap.Put("cronExpression", category.CronExpression);jobDetail.JobDataMap.Put("categoryCode", category.Code);//新建一个触发器ITrigger trigger = TriggerBuilder.Create().StartNow().WithCronSchedule(category.CronExpression).Build();//任务和触发器关联放入调度器scheduler.ScheduleJob(jobDetail, trigger);scheduler.Start();break;}}}}

其实,我实际的项目要复杂很多,业务在不断的变化着,代码也改了很多次,深层的讲解我就不做了(主要我研究的也不深入),我想各位应该看的明白,截图看下吧

目前我的项目中,已经扩展到两部分,一部分是监控引擎,一部分是数据监控,所谓数据监控,是很多很多的方案,每个方案对应几个Task,每个task就是sql或者mdx等查询数据的脚本,还好它们可以用一个Job类实现IJob去处理,而监控引擎则比较复杂,每个Job都做着各自不同的事情,所以,我为每个Job创建Job类,让它们职责单一,如果以后扩展,就新建一个Job类,然后改一下工厂类即可,这样也符合开闭原则。

我给程序设置半小时一次的定时器,查询数据库是否有新增或禁用、修改的Job,每次和静态缓存的对象对比,以实现增删改Job之后,动态Update新的Job,该新增就新增,该禁用就禁用,如果大家有更好的思路,请一定要分享给我喔,谢谢!

再展示一下上端代码:

public class MonitorStartEngine{private static List<Config_Frequency> cacheList;public void Start(){LogHelper.Info("----------------監控引擎windows服務定時執行----------------");try{//在此查詢配置項List<Config_Frequency> queryList = MonitorFrequencyDAL.GetAllFrequencyList();if (queryList != null){queryList.ForEach(action =>{Config_Frequency msf = GetConfig_Frequency(action.MonitorCategory);IJobMonitor monitor = MonitorJobFactory.GetMonitor(action.MonitorCategory);if (monitor != null) monitor.StartJob(msf, action);});}if (cacheList != null){//刪除已禁用的Jobvar deletedJobs = ListCompare(cacheList, queryList);deletedJobs.ForEach(action =>{Config_Frequency msf = GetConfig_Frequency(action.MonitorCategory);IJobMonitor monitor = MonitorJobFactory.GetMonitor(action.MonitorCategory);if (monitor != null) monitor.DeleteJob();});}cacheList = queryList;}catch (Exception ex){LogHelper.Error("監控引擎異常信息: " + ex.Message);QuartzManage.ShutDownJob();}}private Config_Frequency GetConfig_Frequency(string monitorCatogory){if (cacheList != null)return cacheList.Where(c => c.MonitorCategory == monitorCatogory).FirstOrDefault();elsereturn null;}//取出存在cacheList中但不存在queryList中的数据,差异数据放入deletedListprivate List<Config_Frequency> ListCompare(List<Config_Frequency> cacheList, List<Config_Frequency> queryList){List<Config_Frequency> deletedList = new List<Config_Frequency>();if (queryList == null)deletedList = cacheList;else{foreach (Config_Frequency cf in cacheList){var mfs = queryList.Where(c => c.MonitorCategory == cf.MonitorCategory).FirstOrDefault();if (mfs == null){deletedList.Add(cf);}}}return deletedList;}}

最后说一下,如何创建Windows Server:
控制台应用程序发布后,使用管理員身份打開Dos命令:
找到程序exe文件所在目录,比如我本机测试发布后的目录在D:\zPublish\Job
安裝:服務.exe install
卸载:服務.exe uninstall
如圖所示:


http://chatgpt.dhexx.cn/article/TrohDNgp.shtml

相关文章

top命令参数详解

简介 top命令是Linux下常用的性能分析工具&#xff0c;能够实时显示系统中各个进程的资源占用状况&#xff0c;类似于Windows的任务管理器。 top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直…

top命令参数详解(linux top命令的用法详细详解)

通过top命令可以有效的发现系统的缺陷出在哪里。是内存不够、CPU处理能力不够、IO读写过高。 top命令输出长这样&#xff1a; top命令参数详解&#xff08;linux top命令的用法详细详解&#xff09; 以下解析一下各个字段的意思&#xff1a; VIRT&#xff1a;virtual memory …

Topas命令详解

执行topas命令后如图所示&#xff1a; #topas 操作系统的最全面动态&#xff0c;而又查看方便的性能视图就是topas命令了&#xff0c;下面以topas输出为例&#xff0c;对AIX系统的性能监控做简要描述&#xff0c;供运维工程师和系统管理员们参考。 另&#xff1a;1.操作系统报…

Topshelf 打包部署Windows服务

1 创建项目(例&#xff1a;控制台程序) Nuget 引入Topshelf类库 using System; using System.Threading; using System.Threading.Tasks; using Topshelf;namespace LoginTypeInherit {public class Program{private static readonly log4net.ILog log log4net.LogManager.G…

Linux top命令参数详解

Linux top命令参数详解 生产环境系统运行慢&#xff0c;出现无法响应通常原因主要还在于分析CPU、内存、磁盘使用率情况&#xff0c;并结合命令查找出具体进程&#xff0c;并在进程中进一步分析主要因子情况&#xff0c;渗透到对于其中包含线程占用情况的分析。一般而言对于ja…

C#之TopShelf启动Windows服务

写了一两天&#xff0c;才发现组长给的原始代码原本就有Topshelf&#xff0c;还是写出来提示我topshelf不明确哪个版本的使用&#xff0c;莫名尴尬。 1、项目的主要运行代码 HostFactory.Run(x >{x.RunAsLocalSystem();x.SetDescription("topshelf测试");x.SetDi…

Linux下top命令用法详解

一、命令介绍 Linux top命令用于实时显示 process &#xff08;进程&#xff09;的动态。它用于监控正在运行系统负荷的信息&#xff0c;包括系统负载、CPU利用分布情况、内存使用、每个进程的资源占用情况等。 使用权限&#xff1a;所有使用者 二、命令详解 在命令行下输入…

【Linux】Top命令参数解释

TOP命令 这是一个Linux系统下 top 命令所输出的进程监控信息。以下是各列含义&#xff1a; top - 09:52:15&#xff1a;当前时间。 up 27 min&#xff1a;系统已经运行的时长。 2 users&#xff1a;当前有2个用户登录到系统上。 load average: 0.97, 0.41, 0.21&#xff1a;系…

Topshelf的使用

一、简介 Topshelf可用于创建和管理Windows服务。其优势在于不需要创建windows服务&#xff0c;创建控制台程序就可以。便于调试。 二、官方地址&#xff1a; 1、官网&#xff1a;http://topshelf-project.com/ 2、官方文档&#xff1a;https://topshelf.readthedocs.io/en/lat…

topshelf

topshelf和quartz topshelf和quartz内部分享 阅读目录: 介绍基础用法调试及安装可选配置多实例支持及相关资料quartz.net 上月在公司内部的一次分享&#xff0c;现把PPT及部分交流内容整理成博客。 介绍 topshelf是创建windows服务的一种方式&#xff0c;相比原生实现ServiceBa…

使用Quartz.net + Topshelf完成服务调用

概述&#xff1a; Quartz.NET 是一个开源作业调度库&#xff0c;可用于在 .NET 应用程序中调度和管理作业。它提供了一个灵活而强大的框架&#xff0c;用于调度作业在特定的日期和时间或以固定的时间间隔运行&#xff0c;并且还支持复杂的调度场景&#xff0c;例如 cron 表达式…

Linux中top命令参数详解

top命令用法 top命令经常用来监控linux的系统状况&#xff0c;是常用的性能分析工具&#xff0c;能够实时显示系统中各个进程的资源占用情况。 top的使用方式 top [-d number] | top [-bnp] 参数解释&#xff1a; -d&#xff1a;number代表秒数&#xff0c;表示top命令显示…

linux 命令:top 详解

注&#xff1a;以下文档根据2019年10月的官方文档翻译。 名称&#xff1a;top - 展示linux进程信息 用法&#xff1a;top -hv|-bcEHiOSs1 -d secs -n max -u|U user -p pid -o fld -w [cols] 在top进程运行过程中&#xff0c;两个最重要的功能是查看帮助&#xff08;h 或 &…

Topshelf 搭建 Windows 服务

C# Topshelf 搭建 Windows 服务 Topshelf 是一个用来部署基于.NET Framework 开发的服务的框架。简化服务创建于部署过程&#xff0c;并且支持控制台应用程序部署为服务。本文基于 .net core 控制台应用程序部署为服务&#xff08;.net Framework 可用&#xff09;。 第一步&…

TOP命令详解

TOP命令详解 top命令经常用来监控linux的系统状况&#xff0c;是常用的性能分析工具&#xff0c;能够实时显示系统中各个进程的资源占用情况。 top的使用方式 top [-d number] | top [-bnp]参数解释&#xff1a; -d&#xff1a;number代表秒数&#xff0c;表示top命令显示的页…

【转】Topshelf入门

windows服务用处多多啊&#xff0c;wcf宿主服务、webapi宿主服务、定时任务等都会用到&#xff0c;最近写的一套呼叫中心&#xff0c;也最终要做成windows服务&#xff0c;以前都是用vs里面自带的windows服务来写&#xff0c;但感觉总是不够方便&#xff0c;最近了解到了topshe…

使用Topshelf创建Windows服务

概述 Topshelf是创建Windows服务的另一种方法&#xff0c;老外的一篇文章Create a .NET Windows Service in 5 steps with Topshelf通过5个步骤详细的介绍使用使用Topshelf创建Windows 服务。Topshelf是一个开源的跨平台的宿主服务框架&#xff0c;支持Windows和Mono&#xff0…

QT编程入门之QT designer

1.qt 编程的工作流程简介 使用QT作为GUI开发工具的项目流程&#xff0c;可用下图表示&#xff1a; QT项目开发流程 开发流程的解析&#xff1a;假设用QDesigner设计了一个X.UI窗口&#xff0c;之后就需要使用uic来进行编译&#xff0c;生成对应的.h文件.另外一个自定义的类型…

Qt入门学习——Qt Creator 中 ui 文件和 Qt 代码关系

通过《Qt Creator的使用》的学习&#xff0c;我们可以借助 Designer&#xff08;界面设计器&#xff09;快速设计界面。 此例子 ui 内容如下&#xff08;只是简单添加了一个按钮&#xff09;&#xff1a; 工程的代码目录结构如下&#xff1a; 最终在工程所在目录会生成一个 ui …

Qt 设计师 - Qt Designer 调整 控件 居中

目录 问题方案 问题 在上面这张图中&#xff0c;有三个垂直布局&#xff0c;但是垂直布局中上层的控件没办法拖到居中的位置。 方案 点击这个控件&#xff0c;右键 这里2是水平居中&#xff0c;下面的是垂直居中&#xff0c;操作三次&#xff0c;好了