Quartz.Net系列(十四):详解Job中两大特性(DisallowConcurrentExecution、PersistJobDataAfterExecution)
1.DisallowConcurrentExceution
从字面意思来看也就是不允许并发执行
简单的演示一下
[DisallowConcurrentExecution]
public class TestDisallowConcurrentExectionJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Task.Run(() =>
{ var nextTime = context.NextFireTimeUtc?.ToLocalTime(); var currentTime = DateTime.Now; Console.WriteLine($"CurrentTime={currentTime}, FireTime={context.ScheduledFireTimeUtc?.ToLocalTime()}, NextTime={nextTime}"); }); Thread.Sleep(); }
} public class TestDisallowConcurrentExectionScheduler
{
public async static Task Test()
{
var scheduler = await StdSchedulerFactory.GetDefaultScheduler(); await scheduler.Start(); var job = JobBuilder.CreateForAsync<TestDisallowConcurrentExectionJob>()
.WithIdentity("TestDisallowConcurrentExectionJob")
.UsingJobData("myKey", "myValue")
.Build(); var trigger = TriggerBuilder.Create()
.WithSimpleSchedule(s =>
s.WithIntervalInSeconds()
.RepeatForever())
.Build(); await scheduler.ScheduleJob(job, trigger);
}
}
未添加特性的结果

添加特性的结果

Quartz默认是十个线程工作线程加一个调度线程,当线程在等待时,其他工作线程也会进来,而加上DiallowConcurrentExection特性时则都会处于等待状态
原理图

源码解析
通过QuartzSchedulerThread中的Run方法调用AcquireNextTriggers获取下一次的触发器
public virtual Task<IReadOnlyCollection<IOperableTrigger>> AcquireNextTriggers(
DateTimeOffset noLaterThan,
int maxCount,
TimeSpan timeWindow,
CancellationToken cancellationToken = default)
{
lock (lockObject)
{
var result = new List<IOperableTrigger>(); // return empty list if store has no triggers.
if (timeTriggers.Count == )
{
return Task.FromResult<IReadOnlyCollection<IOperableTrigger>>(result);
} var acquiredJobKeysForNoConcurrentExec = new HashSet<JobKey>();
var excludedTriggers = new HashSet<TriggerWrapper>();
DateTimeOffset batchEnd = noLaterThan; while (true)
{
var tw = timeTriggers.FirstOrDefault();
if (tw == null)
{
break;
}
if (!timeTriggers.Remove(tw))
{
break;
} if (tw.Trigger.GetNextFireTimeUtc() == null)
{
continue;
} if (ApplyMisfire(tw))
{
if (tw.Trigger.GetNextFireTimeUtc() != null)
{
timeTriggers.Add(tw);
}
continue;
} if (tw.Trigger.GetNextFireTimeUtc() > batchEnd)
{
timeTriggers.Add(tw);
break;
} // If trigger's job is set as @DisallowConcurrentExecution, and it has already been added to result, then
// put it back into the timeTriggers set and continue to search for next trigger.
JobKey jobKey = tw.Trigger.JobKey;
IJobDetail job = jobsByKey[jobKey].JobDetail;
if (job.ConcurrentExecutionDisallowed)
{
if (!acquiredJobKeysForNoConcurrentExec.Add(jobKey))
{
excludedTriggers.Add(tw);
continue; // go to next trigger in store.
}
} tw.state = InternalTriggerState.Acquired;
tw.Trigger.FireInstanceId = GetFiredTriggerRecordId();
IOperableTrigger trig = (IOperableTrigger) tw.Trigger.Clone(); if (result.Count == )
{
var now = SystemTime.UtcNow();
var nextFireTime = tw.Trigger.GetNextFireTimeUtc().GetValueOrDefault(DateTimeOffset.MinValue);
var max = now > nextFireTime ? now : nextFireTime; batchEnd = max + timeWindow;
} result.Add(trig); if (result.Count == maxCount)
{
break;
}
} // If we did excluded triggers to prevent ACQUIRE state due to DisallowConcurrentExecution, we need to add them back to store.
if (excludedTriggers.Count > )
{
foreach (var excludedTrigger in excludedTriggers)
{
timeTriggers.Add(excludedTrigger);
}
}
return Task.FromResult<IReadOnlyCollection<IOperableTrigger>>(result);
}
}
RAMJobStore中的TriggersFired方法
当添加了DisallowConcurrentExecution特性后
先从TimeTriggers中移除Trigger
再把Job添加BlockedJobs中
if (job.ConcurrentExecutionDisallowed)
{
IEnumerable<TriggerWrapper> trigs = GetTriggerWrappersForJob(job.Key);
foreach (TriggerWrapper ttw in trigs)
{
if (ttw.state == InternalTriggerState.Waiting)
{
ttw.state = InternalTriggerState.Blocked;
}
if (ttw.state == InternalTriggerState.Paused)
{
ttw.state = InternalTriggerState.PausedAndBlocked;
}
timeTriggers.Remove(ttw);
}
blockedJobs.Add(job.Key);
}
RAMJobStore中的TriggeredJobComplete方法
当Job执行完后
如果添加了DisallowConcurrentExecution特性
先移除BlockedJobs中Job 再把Trigger添加到TimeTriggers中
if (jd.ConcurrentExecutionDisallowed)
{
blockedJobs.Remove(jd.Key);
IEnumerable<TriggerWrapper> trigs = GetTriggerWrappersForJob(jd.Key);
foreach (TriggerWrapper ttw in trigs)
{
if (ttw.state == InternalTriggerState.Blocked)
{
ttw.state = InternalTriggerState.Waiting;
timeTriggers.Add(ttw);
}
if (ttw.state == InternalTriggerState.PausedAndBlocked)
{
ttw.state = InternalTriggerState.Paused;
}
} signaler.SignalSchedulingChange(null, cancellationToken);
}
2.PersistJobDataAfterExecution
从字面意思来看也就是执行后保留作业数据
简单演示一下
[PersistJobDataAfterExecution]
public class TestPersistJobDataAfterExecutionJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Task.Run(() =>
{ var myVaule = context.JobDetail.JobDataMap["myKey"]; Console.WriteLine(myVaule); context.JobDetail.JobDataMap["myKey"] = myVaule + new Random().Next(,).ToString();
});
}
} public class TestPersistJobDataAfterExcutionScheduler
{
public async static Task Test()
{
var scheduler = await StdSchedulerFactory.GetDefaultScheduler(); await scheduler.Start(); var job = JobBuilder.CreateForAsync<TestPersistJobDataAfterExecutionJob>()
.WithIdentity("TestPersistJobDataAfterExcutionJob")
.UsingJobData("myKey", "myValue")
.Build(); var trigger = TriggerBuilder.Create()
.WithSimpleSchedule(s =>
s.WithIntervalInSeconds()
.RepeatForever())
.Build(); await scheduler.ScheduleJob(job, trigger);
}
}
未加特性的结果

加特性的结果

原理图

源码解析
QuartzSchedulerThread中的Run方法
JobRunShell shell;
try
{
shell = qsRsrcs.JobRunShellFactory.CreateJobRunShell(bndle);
await shell.Initialize(qs, CancellationToken.None).ConfigureAwait(false);
}
catch (SchedulerException)
{
await qsRsrcs.JobStore.TriggeredJobComplete(trigger, bndle.JobDetail, SchedulerInstruction.SetAllJobTriggersError, CancellationToken.None).ConfigureAwait(false);
continue;
} var threadPoolRunResult = qsRsrcs.ThreadPool.RunInThread(() => shell.Run(CancellationToken.None));
if (threadPoolRunResult == false)
{
// this case should never happen, as it is indicative of the
// scheduler being shutdown or a bug in the thread pool or
// a thread pool being used concurrently - which the docs
// say not to do...
Log.Error("ThreadPool.RunInThread() returned false!");
await qsRsrcs.JobStore.TriggeredJobComplete(trigger, bndle.JobDetail, SchedulerInstruction.SetAllJobTriggersError, CancellationToken.None).ConfigureAwait(false);
}
JobRunShell初始化方法
public virtual async Task Initialize(
QuartzScheduler sched,
CancellationToken cancellationToken = default)
{
qs = sched; IJob job;
IJobDetail jobDetail = firedTriggerBundle.JobDetail; try
{
job = sched.JobFactory.NewJob(firedTriggerBundle, scheduler);
}
catch (SchedulerException se)
{
await sched.NotifySchedulerListenersError($"An error occurred instantiating job to be executed. job= '{jobDetail.Key}'", se, cancellationToken).ConfigureAwait(false);
throw;
}
catch (Exception e)
{
SchedulerException se = new SchedulerException($"Problem instantiating type '{jobDetail.JobType.FullName}'", e);
await sched.NotifySchedulerListenersError($"An error occurred instantiating job to be executed. job= '{jobDetail.Key}'", se, cancellationToken).ConfigureAwait(false);
throw se;
} jec = new JobExecutionContextImpl(scheduler, firedTriggerBundle, job);
}
SimpleJobFactory中的NewJob函数可以看出Job是无状态的直接通过反射创建的
public virtual IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
try
{
if (log.IsDebugEnabled())
{
log.Debug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
} return ObjectUtils.InstantiateType<IJob>(jobType);
}
catch (Exception e)
{
SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
throw se;
}
}
JobRunShell中Run方法将JobExecutionContextImpl塞给了Execute方法
private JobExecutionContextImpl jec;
// Execute the job
try
{
if (log.IsDebugEnabled())
{
log.Debug("Calling Execute on job " + jobDetail.Key);
}
await job.Execute(jec).ConfigureAwait(false);
endTime = SystemTime.UtcNow();
}
JobRunShell中Run方法调用的NotifyJobStoreJobComplete函数
await qs.NotifyJobStoreJobComplete(trigger, jobDetail, instCode, cancellationToken).ConfigureAwait(false);
JobRunShell中的NotifyJobStoreJobComplete 可以看出调用了JobStore.TriggeredJobComplete
public virtual Task NotifyJobStoreJobComplete(
IOperableTrigger trigger,
IJobDetail detail,
SchedulerInstruction instCode,
CancellationToken cancellationToken = default)
{
return resources.JobStore.TriggeredJobComplete(trigger, detail, instCode, cancellationToken);
}
Quartz.NET中StdScheduleFactory如果未指定JobStore则默认RAMJobStore
从而可以看出RAMJobStore中通过TriggerJobComplete方法来检查是否有PersistJobDataAfterExecution特性
如果有通过MemberwiseClone函数克隆出数据来再通过JobBuilder来构建一个JobDetail
if (jobDetail.PersistJobDataAfterExecution)
{
JobDataMap newData = jobDetail.JobDataMap;
if (newData != null)
{
newData = (JobDataMap) newData.Clone();
newData.ClearDirtyFlag();
}
jd = jd.GetJobBuilder().SetJobData(newData).Build();
jw.JobDetail = jd;
}
if (jd.ConcurrentExecutionDisallowed)
{
blockedJobs.Remove(jd.Key);
IEnumerable<TriggerWrapper> trigs = GetTriggerWrappersForJob(jd.Key);
foreach (TriggerWrapper ttw in trigs)
{
if (ttw.state == InternalTriggerState.Blocked)
{
ttw.state = InternalTriggerState.Waiting;
timeTriggers.Add(ttw);
}
if (ttw.state == InternalTriggerState.PausedAndBlocked)
{
ttw.state = InternalTriggerState.Paused;
}
} signaler.SignalSchedulingChange(null, cancellationToken);
}
最终会通过JobRunShell中的Run方法中的ReturnJob方法 返回Job
qs.RemoveInternalSchedulerListener(this);
if (jec != null)
{
if (jec.JobInstance != null)
{
qs.JobFactory.ReturnJob(jec.JobInstance);
} jec.Dispose();
}
public virtual void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
Quartz.Net系列(十四):详解Job中两大特性(DisallowConcurrentExecution、PersistJobDataAfterExecution)的更多相关文章
- java基础(十四)-----详解匿名内部类——Java高级开发必须懂的
在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意的事项.匿名内部类使用的形参为何要为final. 使用匿名内部类内部类 匿名内部类由于没有名字,所以它的创建方式有点儿奇怪.创建格式如下: n ...
- Android笔记(七十四) 详解Intent
我们最常使用Intent来实现Activity之间的转跳,最近做一个app用到从系统搜索图片的功能,使用到了intent的 setType 方法和 setAction 方法,网上搜索一番,发现实现转跳 ...
- 广告行业中那些趣事系列8:详解BERT中分类器源码
最新最全的文章请关注我的微信公众号:数据拾光者. 摘要:BERT是近几年NLP领域中具有里程碑意义的存在.因为效果好和应用范围广所以被广泛应用于科学研究和工程项目中.广告系列中前几篇文章有从理论的方面 ...
- 爬虫系列 | 6、详解爬虫中BeautifulSoup4的用法
bs4,全称BeautifulSoup 4 , 它是Python独有的一种解析方式.也就是说只有Python语言才可以通过这种方式去解析数据. BeautifulSoup 3 只支持Python2,所 ...
- 详解Redis中两种持久化机制RDB和AOF(面试常问,工作常用)
redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发生丢失.幸好Redis还为我们提供了持久化的机制,分别是RDB(Redis DataBase)和AOF(Ap ...
- 详解Redis中两种持久化机制RDB和AOF
redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发生丢失.幸好Redis还为我们提供了持久化的机制,分别是RDB(Redis DataBase)和AOF(Ap ...
- “全栈2019”Java第八十四章:接口中嵌套接口详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Hexo系列(二) 配置文件详解
Hexo 是一款优秀的博客框架,在使用 Hexo 搭建一个属于自己的博客网站后,我们还需要对其进行配置,使得 Hexo 更能满足自己的需求 这里所说的配置文件,是位于站点根目录下的 _config.y ...
- 十图详解tensorflow数据读取机制(附代码)转知乎
十图详解tensorflow数据读取机制(附代码) - 何之源的文章 - 知乎 https://zhuanlan.zhihu.com/p/27238630
随机推荐
- python实现批量文件重命名
本文实例为大家分享了python批量文件重命名的具体代码,供大家参考,具体内容如下 问题描述 最近遇到朋友求助,如何将大量文件名前面的某些字符删除. 即将图中文件前的编号删除. Python实现 用到 ...
- c常用函数-sizeof
sizeof 函数用来返回指定表达式.变量或指定数据类型在内存中所占有的字节数 接下来分析sizeof的计算过程: "abcde"是字符串,考虑到系统自动添加了结束符"\ ...
- java 拦截器解决xss攻击
一.xss攻击 XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序.这些恶意网页程序通常是JavaScript,但实际上也 ...
- 一篇文章教会你使用Python定时抓取微博评论
[Part1--理论篇] 试想一个问题,如果我们要抓取某个微博大V微博的评论数据,应该怎么实现呢?最简单的做法就是找到微博评论数据接口,然后通过改变参数来获取最新数据并保存.首先从微博api寻找抓取评 ...
- LNMP 环境更换Nginx 服务器为Tengine
本人之前所使用 LNMP 环境一直是原生的Nginx服务器,最近几天看了好多大网站使用 淘宝团队基于Nginx开发的 Tengine 决定给自己的虚拟机也装个玩玩. 关于Tengine的介绍就不多说了 ...
- laravel查询常用的方式含义.
find($id) 传值并返回一个模型.如果不存在匹配的模型,则返回null.findOrFail($id) 传值并返回一个模型.如果不存在匹配的模型, 它会抛出异常.first() 返回在数据库中找 ...
- cute-cnblogs 一期样式原文
cute-cnblogs 说明 "我经常有那种感觉,如果这个事情来了,你却没有勇敢地去解决掉,它一定会再来.生活真是这样,它会一次次地让你去做这个功课直到你学会为止." -- &l ...
- C/S C# WPF锐浪报表教程
前言:锐浪报表是一种中国式报表的报表开发工具.博主使用锐浪报表有一段时间了,积累了一些经验希望能帮助你快速掌握并使用 第一章:集成项目 首先我们先去锐浪报表官网下载并安装锐浪报表. 创建WPF应用程序 ...
- Beta冲刺<10/10>
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 Beta冲刺 这个作业的目标 Beta冲刺--第十天(05.28) 作业正文 如下 其他参考文献 ... B ...
- springboot 集成mybatis时日志输出
application.properties(yml)中配置的两种方式: 这两种方式的效果是一样的,但是下面一种可以指定某个包下的SQL打印出来,上面这个会全部的都会打印出来.