ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

在 Worker Service 中托管 Quartz.NET:从 AddQuartz 到 Trim Canary 的完整实战指南

在 Worker Service 中托管 Quartz.NET:从 AddQuartz 到 Trim Canary 的完整实战指南 任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载导读本文围绕 src/Quartz.Examples.Worker 这一最小但真实的托管示例展开它是 Quartz.NET 官方文档中 Hosted Services Integration 所描述集成方式的一个可运行落地项目。读完本文你将掌握如何用Microsoft.NET.Sdk.Worker模板把 Quartz 注册为IHostedService、如何用ScheduleJobT与AddJobAddTrigger两种方式描述调度、如何注册三类监听器、如何借助StartDelay协调多个后台服务以及这个示例项目为何同时扮演着仓库的Trim Canary裁剪金丝雀角色。项目概览一个最小但完整的托管调度器src/Quartz.Examples.Worker是一个Microsoft.NET.Sdk.Worker项目见 Quartz.Examples.Worker.csproj核心代码全部集中在几个文件里文件展示的内容Program.cs在HostApplicationBuilder上调用AddQuartz与AddQuartzHostedService配置线程池、简单类型加载器以及两种描述调度的方式ExampleJob.cs一个普通 Job每次触发时都从 DI 容器解析Listeners.cs分别用AddJobListener/AddTriggerListener/AddSchedulerListener注册的 Job、Trigger 与 Scheduler 监听器Worker.cs调度器之外的第二个后台服务这正是设置StartDelay的原因appsettings.jsonQuartz配置节在AddQuartz回调执行之前先被应用这个项目不依赖任何外部存储——调度器使用内存 JobStore开箱即跑。运行它零依赖启动在仓库根目录执行dotnet run --project src/Quartz.Examples.Worker无需任何外部设施数据库、Redis、消息队列都不需要因为 JobStore 是内存型的。需要注意 Program.cs 中的十秒StartDelay主机启动后约十秒第一个 Job 才会触发这是刻意为之——该配置的意义就在于让另一个IHostedService即 Worker.cs先完成初始化。运行后你会看到 Serilog 输出的三类日志Worker running at: ...来自独立后台服务、Trigger ... fired/Job ... to be executed来自监听器、以及ExampleJob job executing, triggered by ...来自 Job 本体ExampleJob执行时还会模拟一秒钟的工作Task.Delay(TimeSpan.FromSeconds(1), ...)。注册管线AddQuartz 与 AddQuartzHostedService从 HostApplicationBuilder 一路到底层Program.cs展示了 Quartz.NET 4.x 推荐的托管集成方式HostApplicationBuilder builder Host.CreateApplicationBuilder(args); builder.Logging.ClearProviders(); builder.Services.AddSerilog(); builder.Services.AddHostedServiceWorker(); builder.AddQuartz(q { /* ... */ }); builder.AddQuartzHostedService(options { /* ... */ }); builder.Build().Run();AddQuartz是 QuartzHostApplicationBuilderExtensions 提供的扩展方法。它的关键在于builder 同时持有服务集合与配置因此builder.AddQuartz(…)会自动读取名为Quartz的配置节源码中ConfigurationSectionName Quartz无需像老式写法那样手动传入builder.Configuration.GetSection(Quartz)。这意味着 appsettings.json 中的Quartz节内容会先于回调q { ... }被应用回调再在此基础上覆盖或补充——两层配置按配置文件优先、代码回调兜底的顺序生效。AddQuartzHostedService则负责把调度器包装成IHostedService随应用的启动/停止而启停其行为由 QuartzHostedServiceOptions 控制主要选项如下选项类型默认值说明WaitForJobsToCompleteboolfalse为true时关闭进程会等待所有正在执行的 Job 完成后再返回StartDelayTimeSpan?null非null时调度器在指定延迟后启动若AwaitApplicationStarted为 true延迟从应用启动完成后开始计时AwaitApplicationStartedbooltrue为 true默认时Job 不会在应用启动过程中被触发避免启动期的竞态AutoStartbooltrue为false时调度器只被构建、初始化并绑定但停在SchedulerStatus.Created由应用择机调用IScheduler.Start()适合自管主从选举的库Program.cs中的两个设置分别对应两个典型诉求builder.AddQuartzHostedService(options { options.WaitForJobsToComplete true; // 关停时让进行中的 Job 优雅结束 options.StartDelay TimeSpan.FromSeconds(10); // 等另一个 IHostedService 先初始化 });基础配置实例 ID、类型加载器、内存存储与线程池builder.AddQuartz(q { q.ConfigureScheduler(options options.InstanceId Scheduler-Core); q.UseSimpleTypeLoader(); q.UseInMemoryStore(); q.UseDefaultThreadPool(tp tp.MaxConcurrency 10); });ConfigureScheduler(options options.InstanceId Scheduler-Core)设置调度器实例 ID在集群或多调度器场景下用于区分彼此UseSimpleTypeLoader()简单类型加载器直接使用当前Assembly解析 Job 类型这是默认行为UseInMemoryStore()内存 JobStore进程重启后调度数据即丢失适合示例与无状态场景UseDefaultThreadPool(tp tp.MaxConcurrency 10)默认线程池最大并发线程数设为 10。两种描述调度的方式并排展示Program.cs刻意把两种调度描述方式放在一起展示方式一ScheduleJobT—— 一个 Job 配一个 Triggerq.ScheduleJobExampleJob(trigger trigger .WithIdentity(Combined Configuration Trigger) .StartAt(DateTimeOffset.UtcNow.AddSeconds(1)) .WithDailyTimeIntervalSchedule(x x.WithInterval(10, IntervalUnit.Second)) .WithDescription(my awesome trigger configured for a job with single call) );这是最快路径一次调用同时声明 Job 与其唯一 Trigger。这里使用DailyTimeIntervalSchedule每隔 10 秒触发一次WithIdentity给 Trigger 命名StartAt指定从启动后 1 秒开始。方式二AddDeclaredJobs()—— 在 Job 类上声明调度q.AddDeclaredJobs();这一行会添加当前程序集中所有以声明式特性描述的 Job。声明就写在 ExampleJob.cs 的类上[QuartzJob(Name ExampleJob, Description my awesome declared job)] [CronTrigger(0/10 * * * * ?, Description my awesome declared trigger)] public class ExampleJob : IJob, IDisposable { private readonly ILoggerExampleJob logger; public ExampleJob(ILoggerExampleJob logger) { this.logger logger; } public async ValueTask Execute(IJobExecutionContext context, CancellationToken cancellationToken default) { logger.LogInformation({Job} job executing, triggered by {Trigger}, context.JobDetail.Key, context.Trigger.Key); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); } public void Dispose() { GC.SuppressFinalize(this); logger.LogInformation(Example job disposing); } }[QuartzJob]与[CronTrigger]特性由 Quartz 的源生成器DeclaredJobsGenerator.cs在编译期转换为AddJobExampleJob与AddTriggerExampleJob的注册代码——也就是Program.cs曾需要手写的那些调用。生成代码位于obj/目录下可自行查阅。由于生成器随 NuGet 包分发这个示例项目还在 Quartz.Examples.Worker.csproj 中以ProjectReferenceReferenceOutputAssemblyfalse OutputItemTypeAnalyzer的方式显式引用了Quartz.Analyzers项目以模拟真实消费者的包内分析器体验。注意两种方式各有一个 Trigger 在生效Combined Configuration Trigger每 10 秒与0/10 * * * * ?每 10 秒整秒它们在时间上错开让日志更易区分两条触发链路。监听器注册三类监听器一次配齐Listeners.cs 定义了三个监听器Program.cs用三个扩展方法注册q.AddTriggerListenerTestTriggerListener(); q.AddJobListenerTestJobListener(); q.AddSchedulerListenerTestSchedulerListener();TestSchedulerListener : ISchedulerListener只覆写了SchedulerStarting在调度器启动时记录Scheduler {SchedulerName} startingTestJobListener : IJobListener实现Name属性监听器必须有名字在JobToBeExecuted中记录 Job 即将执行TestTriggerListener : ITriggerListener在TriggerFired中记录 Trigger 已触发。三者均为带ILoggerT构造参数的类由 DI 容器解析——这印证了 Quartz.NET 4.x 中监听器本身也是容器服务这一设计。配置节appsettings.json 中的 Quartzappsettings.json 中声明了调度器实例名{ Logging: { LogLevel: { Default: Information, Microsoft: Warning, Microsoft.Hosting.Lifetime: Information } }, AllowedHosts: *, Quartz: { quartz.scheduler.instanceName: Quartz Worker Service Sample Scheduler } }Quartz配置节中的键遵循 Quartz 传统属性命名quartz.scheduler.instanceName在AddQuartz回调执行前即被绑定到QuartzOptions。Program.cs中的IgnoreDuplicates设置同样走这一机制builder.Services.ConfigureQuartzOptions(options { options.Scheduling.IgnoreDuplicates true; // default: false });IgnoreDuplicates的含义是当声明的 Job 或 Trigger 的 Key 已存在于存储中时跳过而不是覆盖它。需要特别注意的是QuartzOptions的校验逻辑见 QuartzOptionsValidators.cs会在同时显式设置OverwriteExistingData true与IgnoreDuplicates true时拒绝启动——因为这两个选项是同一问题的相反答案OverwriteExistingData默认即为 true默认值不视为显式声明单独设置IgnoreDuplicates即可将其关闭但同时写死两者会直接抛错。这也解释了示例中注释的提示IgnoreDuplicates默认为false。双重身份Trim Canary裁剪金丝雀这个项目除了是托管示例还承担着仓库的Trim Canary职责它是dotnet publish以PublishTrimmedTrimModefull发布的最小裁剪应用见 Quartz.Examples.Worker.csproj其存在目的在 csproj 的注释中讲得很清楚一个只裁剪 Quartz 且不含其他任何东西的完整应用因此发布报告出的每一个IL2xxx都是 Quartz 自己的问题。关键机制不按警告码屏蔽过去项目曾对整个IL2xxx系列NoWarn那只能证明示例能发布而非Quartz 经得起裁剪issue #3341 的第一步现在已不再按警告码屏蔽任何内容。按类型集合抑制被抑制的是记录在 src/Quartz/ILLink.Suppressions.xml 中的类型集合通过_ILLinkSuppressions项挂接到 SDK 的 ILLink 链接特性文件钩子。一旦某个集合之外的类型开始产生裁剪警告发布就会失败——这正是金丝雀的含义。精确的依赖固定项目显式引用Serilog因为Serilog.Extensions.Hosting传递解析出的 4.3.0 版本在完全裁剪发布下会报IL2104而集中版本Directory.Packages.props中央包管理是裁剪安全的。对改动保持敏感csproj 提示在这里新增一个包引用会改变这条测试腿所衡量的对象因此向此项目加依赖需三思——它会直接改变裁剪覆盖面的含义。小结与延伸阅读Quartz.Examples.Worker用不到百行代码演示了 Quartz.NET 在 .NET 托管环境中的完整最小闭环AddQuartz读配置、回调配调度器、AddQuartzHostedService管生命周期、ScheduleJob/ 声明式特性两种建 Job 方式、三类监听器、以及兼顾优雅关停与多服务协调的托管选项。它同时是验证库级裁剪安全性的重要一环。想进一步深入可在仓库中继续阅读托管集成官方文档Hosted Services Integration 的对应页面docs/documentation/quartz-4.x 目录可找到更多主题托管服务实现QuartzHostedService.cs 与 QuartzServiceCollectionExtensions.cs声明式 Job 的源生成器DeclaredJobsGenerator.cs 及其测试 DeclaredJobsGeneratorTest.cs同类但更完整的托管示例Quartz.Examples.Worker 之外还有 Quartz.Examples.AspNetCore 展示 Web 场景下的集成。赞分享任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载相关推荐TVBoxOSC终极指南如何让普通电视盒子变身全能媒体中心TVBoxOSC终极指南如何让普通电视盒子变身全能媒体中心 还在为电视盒子无法流畅播放各种视频格式而烦恼吗 TVBoxOSC 这款开源电视盒子管理方案通过Canary Token Generator自托管蜜令牌服务完整实战指南Go ReactCanary Token Generator自托管蜜令牌服务完整实战指南Go React 本篇指南以开源仓库中的 canary token gener终极指南如何构建Next.js离线应用——从Service Worker到PWA的完整实战终极指南如何构建Next.js离线应用——从Service Worker到PWA的完整实战 Next.js作为React框架的佼佼者不仅提供了卓越的服务端渲前端后端Web框架SSR前端构建上一篇libSQL 深度指南SQLite 的开源开放贡献分支及其核心扩展解析下一篇pybind11 版本发布指南从版本号规范到完整的发布流程实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表