Quartz.NET 任务进阶:JobDetail、JobDataMap 与作业并发状态控制完全指南
任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载本篇技术指南聚焦 Quartz.NET 3.x 教程第四课“More About Jobs”的核心内容理解IJob作业类与JobDetail定义的关系、用JobDataMap为作业传参和保存状态、借助[DisallowConcurrentExecution]与[PersistJobDataAfterExecution]控制作业的并发与状态持久化以及用JobExecutionException向调度器下达异常处理指令。读完本文你将掌握如何把一次编写、多处复用的作业类配置成多个独立调度定义并写出状态安全、并发可控的生产级 Quartz.NET 作业。本课是 Quartz.NET 3.x 官方教程的第四课建议先阅读 Lesson 1使用 Quartz 与 Lesson 3作业与触发器再回到本文深入作业内部的细节。作业类与 JobDetail一次执行一个实例你的作业类job class承载的是真正干活的代码而调度器还需要知道每个作业实例的属性——这些属性存放在JobDetail中通过流式的JobBuilder构建。回顾第一课的代码// 定义作业并把它绑定到我们的 HelloJob 类 IJobDetail job JobBuilder.CreateHelloJob() .WithIdentity(myJob, group1) .Build(); // 让触发器立即触发之后每 40 秒触发一次 ITrigger trigger TriggerBuilder.Create() .WithIdentity(myTrigger, group1) .StartNow() .WithSimpleSchedule(x x .WithIntervalInSeconds(40) .RepeatForever()) .Build(); await sched.ScheduleJob(job, trigger);作业类HelloJob长这样public class HelloJob : IJob { public async Task Execute(IJobExecutionContext context) { await Console.Out.WriteLineAsync(HelloJob is executing.); } }调度器拿到的是一份IJobDetail它记录了作业类的名称。每次执行作业时Quartz.NET 都会在调用Execute(..)之前为该类创建一个新的实例。由此得出两条硬性结论作业类必须有无参构造函数no-argument constructor作业类上的数据字段毫无意义——因为它们的值不会在上一次执行与下一次执行之间保留。[!TIP] 依赖注入 如果使用了依赖注入框架构造函数可以像 ASP.NET MVC 中的控制器一样接收服务依赖而无参构造的要求由 DI 容器代为满足。从源码看JobBuilder.cs 中的JobBuilder.CreateT()要求T : IJobBuild()时若没有显式调用.WithIdentity(...)会为你生成一个随机的Guid作为JobKey这印证了构建器始终能以一个合法状态产出 JobDetail的设计。若作业执行时彼此需要不同的输入或要在多次执行之间保留状态就要用到JobDetail上的JobDataMap。JobDataMap给作业传参、为作业保存状态JobDataMap可以存放任意数量可序列化的对象供作业实例在Execute时使用。它实现了IDictionary接口并额外提供了一组便捷方法用于存取基本类型primitive types。其定义位于 JobDataMap.cs内部基于DirtyFlagMap实现并带有脏标记机制——当作业的JobDataMap被修改时脏标记会告知调度器需要重新持久化。写入在把作业加入调度器之前放入数据// 定义作业并把它绑定到我们的 DumbJob 类 IJobDetail job JobBuilder.CreateDumbJob() .WithIdentity(myJob, group1) // 名称 myJob分组 group1 .UsingJobData(jobSays, Hello World!) .UsingJobData(myFloatValue, 3.141f) .Build();读取在作业执行期间取出数据public class DumbJob : IJob { public async Task Execute(IJobExecutionContext context) { JobKey key context.JobDetail.Key; // 注意生产代码请使用 context.MergedJobDataMap JobDataMap dataMap context.JobDetail.JobDataMap; string jobSays dataMap.GetString(jobSays); float myFloatValue dataMap.GetFloat(myFloatValue); await Console.Error.WriteLineAsync( Instance key of DumbJob says: jobSays , and val is: myFloatValue); } }类型化的读取方法GetString、GetFloat、GetInt、GetBoolean、GetDateTimeOffset以及TryGet*、泛型的GetT等定义在 DataMapExtensions.cs 中。该文件源码显示每个访问器都同时兼容以原类型存储的值与以字符串存储的值——在StoreJobDataAsStrings true的存储模式下所有数据都以不变文化invariant culture字符串落库读取时会先匹配存储类型再按CultureInfo.InvariantCulture解析字符串。因此同一份读取代码无论数据是以对象形式还是字符串形式持久化的都能正确工作。持久化 JobStore 下的序列化注意事项如果使用了持久化 JobStore参见 Job Stores放入JobDataMap的内容要仔细斟酌其中的对象会被序列化因此容易遭遇类版本兼容问题。标准 .NET 类型是安全的对于你自己定义的类任何对已有序列化实例的类的修改都必须保证兼容性不被破坏例如不删除字段、不改变字段语义。AdoJobStore与JobDataMap可以切换到一种只存原始类型与字符串的模式从根本上规避日后的序列化问题。该开关正是StoreJobDataAsStrings配置项在 AdoJobStoreBase.cs 中它被读取为usePropertiesStdAdoDelegate.cs 中的CanUseProperties即由此决定。注意源码注释强调一个已经以 blob 形式写入数据的存储不能直接切换到字符串模式而不重写数据所以该选项应在部署初期就定好。属性注入让 JobFactory 替你赋值如果你的作业类中有公有set访问器、且属性名与JobDataMap的键一致的属性Quartz.NET 的默认JobFactory会在实例化作业时自动调用这些 setter于是Execute中就不再需要手动读取 Map。自定义的JobFactory默认不会做这件事。public class DumbJob : IJob { public string JobSays { private get; set; } public float MyFloatValue { private get; set; } public async Task Execute(IJobExecutionContext context) { JobKey key context.JobDetail.Key; JobDataMap dataMap context.MergedJobDataMap; // 注意与上一例的差别 IListDateTimeOffset state (IListDateTimeOffset)dataMap[myStateData]; state.Add(DateTimeOffset.UtcNow); await Console.Error.WriteLineAsync( Instance key of DumbJob says: JobSays , and val is: MyFloatValue); } }类变得更长了但Execute()内部更干净。除了按字符串键写入JobBuilder.cs 还提供了强类型的UsingJobDataTValue(ExpressionFuncTJob, TValue jobProperty, TValue value)重载——直接以表达式指定作业属性而非拼写键名避免键名拼错与类型不符枚举属性会按枚举名存储。Trigger 也有 JobDataMap以及 MergedJobDataMap 的合并规则触发器也可以拥有自己的JobDataMap。当一个已存储的作业被多个触发器触发、且每个触发器要传入不同数据时就该把数据放在触发器的 Map 里。JobExecutionContext上的JobDataMap是JobDetail的JobDataMap与Trigger的JobDataMap合并的结果触发器中同名键的值会覆盖作业中的值。合并逻辑见 JobExecutionContextImpl.cs 的注释——它是 JobDetail 上的 Map 与 Trigger 上的 Map 的合并后者的同名值覆盖前者因此作业的Execute代码从该 Map 取数据被视为最佳实践。同时该实现也明确指出这个 Map 是本次触发firing专属的一份拷贝写入它是安全的作业、中间件、监听器都能看到但任何修改都不会回写到 JobDetail 或 Trigger 的已存储数据。读取合并后的 JobDataMappublic class DumbJob : IJob { public async Task Execute(IJobExecutionContext context) { JobKey key context.JobDetail.Key; JobDataMap dataMap context.MergedJobDataMap; // 注意与上一例的差别 string jobSays dataMap.GetString(jobSays); float myFloatValue dataMap.GetFloat(myFloatValue); IListDateTimeOffset state (IListDateTimeOffset)dataMap[myStateData]; state.Add(DateTimeOffset.UtcNow); await Console.Error.WriteLineAsync( Instance key of DumbJob says: jobSays , and val is: myFloatValue); } }作业实例一个类多个定义一个作业类可以在调度器中拥有许多实例定义创建多个JobDetail每个都有自己的属性与JobDataMap然后把它们全部加入调度器即可。例如作业类SalesReportJob从自己的JobDataMap读取销售员姓名。你可以创建两个JobDetail——SalesReportForJoyce和SalesReportForMike分别把Joyce与Mike放进各自的JobDataMap。当触发器触发时其关联的 JobDetail 会被加载作业类由调度器的JobFactory实例化默认JobFactory用Activator.CreateInstance调用作业类的默认构造函数再调用与JobDataMap键匹配的 setter 属性。编写自己的JobFactory可以实现让 IoC/DI 容器来创建并初始化作业实例Quartz.NET 3.x 中MicrosoftDependencyInjectionJobFactory正是如此可配合扩展包使用。术语约定术语含义job definition 或 JobDetail instance已存储的 JobDetailjob instance 或 instance of a job definition正在执行的作业job通常指一个具名的定义即 JobDetailjob type实现作业接口的类作业状态与并发两个关键特性作业类上的两个特性attribute会改变 Quartz.NET 对作业状态即 JobDataMap与并发的处理方式。[DisallowConcurrentExecution]告诉 Quartz.NET不要并发执行同一个作业定义的多个实例。它作用于每个 JobDetail按定义而不是按作业类如果SalesReportJob标注了该特性同一时刻只会有一个SalesReportForJoyce在执行但SalesReportForMike可以与它并行运行。特性之所以写在类上是因为它往往会影响类的编写方式。[DisallowConcurrentExecution] public class SalesReportJob : IJob { public async Task Execute(IJobExecutionContext context) { // 同一 JobDetail 定义不会并发执行 } }从 DisallowConcurrentExecutionAttribute.cs 的源码看该特性的AttributeUsage允许Class | Interface——即可以标注在作业类上也可以标注在接口上便于对一组实现同一接口的作业统一约束。JobBuilder.cs 还提供了等价的流式方法.DisallowConcurrentExecution(bool)若未显式设置调度器会在作业类型自身、其祖先类或实现的接口标注了该特性时禁止并发。[PersistJobDataAfterExecution]告诉 Quartz.NET 在Execute()完成之后更新 JobDetail 已存储的JobDataMap——即使Execute抛出了JobExecutionException也会更新。这样同一个 JobDetail 的下一次执行拿到的就是更新后的值而不是最初存储的值。它同样作用于每个作业定义并且标注在类上因为Execute中的代码必须为有状态而设计。[PersistJobDataAfterExecution] public class StatefulJob : IJob { public async Task Execute(IJobExecutionContext context) { var dataMap context.MergedJobDataMap; int count dataMap.GetInt(count) 1; dataMap[count] count; // 本次执行后写回 JobDetail 的存储 } }其定义见 PersistJobDataAfterExecutionAttribute.csAttributeUsage同样是Class | InterfaceJobBuilder.cs 也提供了.PersistJobDataAfterExecution(bool)方法。注意 JobDataMap.cs 的注释带该特性的作业实例每次执行后其JobDataMap会被重新持久化——这正是脏标记机制发挥作用的地方。两者的搭配建议如果你使用了PersistJobDataAfterExecution强烈建议同时使用[DisallowConcurrentExecution]。否则当同一个 JobDetail 的两个实例并发运行时最终存储下来的是哪一份数据将成为一场竞态race。这是因为两次并发执行都可能在互相覆盖 JobDataMap 的写入结果。JobDetail 的其他属性你还可以在 JobDetail 上设置其他属性属性效果Durability非持久non-durable作业在没有与之关联的活动触发器时会被调度器自动删除。RequestsRecovery若作业在硬关机hard shutdown期间正在执行调度器再次启动时会重新执行该作业。硬关机hard shutdown指的是进程崩溃或机器断电。当作业以这种方式被重新执行时JobExecutionContext.Recovering会返回true该属性定义于 IJobExecutionContext.cs实现中还会从合并后的 JobDataMap 恢复出原触发器的TriggerKey。在 JobBuilder.cs 中两者分别对应.StoreDurably(bool)与.RequestRecovery(bool)流式方法默认值都是false。JobExecutionException向调度器下达指令从IJob.Execute(..)中唯一可以抛出的异常就是JobExecutionException因此通常的做法是把整个方法体包进 try-catch。JobExecutionException还可以携带给调度器的处理指令——详见其文档与下面的源码说明。JobExecutionException.cs 定义了三个 init-only 指令属性用对象初始化器设置// 立即以同一个 JobExecutionContext 重新触发 throw new JobExecutionException(import failed, ex) { RefireImmediately true };指令属性含义RefireImmediately立即以同一个IJobExecutionContext重新触发该作业若该标志为 true以下两个注销指令会被忽略。UnscheduleFiringTrigger注销触发本次执行的触发器。UnscheduleAllTriggers注销该作业的所有触发器。此外该类型是sealed的有意设计为对调度器的三条指令作业自己附加的状态应通过Exception.Data携带例如throw new JobExecutionException(import failed, ex) { Data { [tenant] tenantId } }作业抛出的实例会原样交给IJobListener.JobWasExecuted。若抛出的不是JobExecutionException则会以JobExecutionException→JobExecutionProcessException→ 你的异常的两层包装形式到达监听器。小结作业类是干活的模板JobDetail才是调度器眼中的定义两者通过JobBuilder绑定每次执行都会新实例化作业类因此无参构造函数是硬性要求。JobDataMap是作业传参与状态保存的唯一官方通道JobDetail 与 Trigger 各有一份执行期读取MergedJobDataMap获得合并视图Trigger 同名值覆盖 Job写入只影响本次 firing需要跨执行保留状态则配合[PersistJobDataAfterExecution]。一个作业类可注册多个JobDetail如SalesReportForJoyce/SalesReportForMike实现同类不同数据的复用。并发与状态用[DisallowConcurrentExecution]与[PersistJobDataAfterExecution]控制二者通常应成对使用。作业的异常处理统一收敛到JobExecutionException其三个指令属性RefireImmediately、UnscheduleFiringTrigger、UnscheduleAllTriggers让作业能把接下来怎么办的决定权交回调度器。延伸阅读Lesson 5更多关于触发器——触发器的JobDataMap、优先级与 misfire 指令Lesson 10JobStores——RAMJobStore 与 AdoJobStore以及StoreJobDataAsStrings的完整上下文核心源码JobBuilder.cs、JobDataMap.cs、JobExecutionContextImpl.cs、JobExecutionException.cs赞分享任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载相关推荐终极指南Quartz.NET任务参数传递与状态管理的JobDataMap完全掌握终极指南Quartz.NET任务参数传递与状态管理的JobDataMap完全掌握 Quartz.NET是一个功能强大的企业级任务调度框架而JobDataMa任务调度后端Quartz.NET 任务与触发器Jobs and Triggers核心概念详解IJob、JobDetail、JobDataMap 与 Trigger 编程模型Quartz.NET 任务与触发器Jobs and Triggers核心概念详解IJob、JobDetail、JobDataMap 与 Trigger 编任务调度后端如何用Chunky快速预生成Minecraft区块新手必备的完整指南如何用Chunky快速预生成Minecraft区块新手必备的完整指南 Chunky是一款专为Minecraft服务器设计的区块预生成工具能够帮助服务器管理员上一篇社区贡献指南如何参与tiny-random-mistral-openmind项目的开发与改进下一篇ExplorerPatcher深度解析Windows 11 23H2累积更新兼容性危机与解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

大语言模型提示词工程实战:从原理到应用全攻略

大语言模型提示词工程实战:从原理到应用全攻略

最近大半年,我基本把业余时间都砸在了大语言模型相关的学习和实践上,尤其是提示词工程这一块。说句实话,真正自己上手去调 prompt 之后,才发现这远比想象中有意思,也远比想象中更讲究方法。同一个问题,不同…

2026/10/19 4:47:44 阅读更多 →
DeepSeek Harness 桌面版实战:Agent 工作流可视化、配置与内网部署

DeepSeek Harness 桌面版实战:Agent 工作流可视化、配置与内网部署

DeepSeek Harness 桌面版正式发布后,我第一时间下载装到了主力机上。这个“开箱即用”不是宣传话术,装完、配好模型接口、点一下启动,就能把之前散落在命令行里的 Agent 工作流全部搬到图形界面上。对于每天要写综述、调提示词、跑代码回退的…

2026/10/18 15:01:40 阅读更多 →
DroidPlugin 插件化源码前奏:系统 Activity 启动流程深度解析与占坑 Hook 的切入点

DroidPlugin 插件化源码前奏:系统 Activity 启动流程深度解析与占坑 Hook 的切入点

移动开发插件系统 【免费下载链接】DroidPlugin A plugin framework on android,Run any third-party apk without installation, modification or repackage 项目地址: https://gitcode.com/gh_mirrors/dro/DroidPlugin 点击查看 免费下载 本文基于仓库文档 DOC/h…

2026/10/18 16:29:17 阅读更多 →

最新新闻

CentOS下BIND域名解析配置实战:从正向/反向解析到故障排查

CentOS下BIND域名解析配置实战:从正向/反向解析到故障排查

1. 项目概述:CentOS下的域名解析配置到底要做什么第五个年头做Linux运维,最常见的需求之一就是“帮我把域名解析配置上”。这句话在上线部署、内网服务联调、测试环境搭建里反复出现。尤其是新买了一台CentOS服务器,业务程序部署完了&#xf…

2026/10/19 4:48:02 阅读更多 →
select与poll深度剖析:从原理到实战,理解I/O多路复用核心机制

select与poll深度剖析:从原理到实战,理解I/O多路复用核心机制

早几年我刚入行做服务端开发的时候,接手了一个老项目,核心网络模块就是拿select写的。那时候我并没有多想,只觉得它把我从"一个连接一个线程"的泥潭里捞了出来,代码也能跑,线上也没啥大问题。直到后来自己写…

2026/10/19 4:48:02 阅读更多 →
Kafka集群部署实战:从单节点到高可用配置全解析

Kafka集群部署实战:从单节点到高可用配置全解析

1. 部署前的思维准备:Kafka 到底是怎么跑起来的很多人在第一次接触 Kafka 安装部署时,习惯直接上网搜一条命令复制粘贴,结果不是版本对不上,就是启动报一堆看不懂的异常。我在最开始踩坑时也这样,后来花了很长时间把 K…

2026/10/19 4:48:02 阅读更多 →
open-code-review实践:构建高效透明的代码审查流程

open-code-review实践:构建高效透明的代码审查流程

1. open-code-review 的命名背后:它到底在表达什么理念我第一次看到 open-code-review 这个项目名时,第一反应是:这不就是把 code review 加了个 open 前缀么?但真把这几个词拆开琢磨一遍,会发现事情没那么简单。它在工…

2026/10/19 4:48:02 阅读更多 →
图像缩放插值算法选型:从最近邻到边缘定向,MATLAB实现与性能对比

图像缩放插值算法选型:从最近邻到边缘定向,MATLAB实现与性能对比

简介:这份毕业论文以数字图像缩放技术为研究主题,面向图像处理学习者与电子信息类本科毕业生,系统介绍了数字图像采集、存储、处理与分析的基本流程,并重点剖析最近邻插值、双线性插值、双三次插值等经典算法,通过MATL…

2026/10/19 4:48:02 阅读更多 →
代码审查自动化实践:open-code-review规则引擎与CI集成详解

代码审查自动化实践:open-code-review规则引擎与CI集成详解

1. 为什么我要折腾一个叫 open-code-review 的东西先交代一下背景。我一直在维护一个十几人的研发团队,代码审查这件事从我开始带队伍起就断断续续做过很多轮,但说实话效果一直不稳定。团队的 Code Review 流程一开始用的是最原始的方式:提 P…

2026/10/19 4:47:01 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →