在Quartz.NET中是否有一种方法可以设置一个属性,只允许运行一个作业实例?

33

我有一个服务,每隔X分钟运行一次。如果由于某些不可预见的原因,该作业需要超过X分钟,我希望确保触发器不会启动此作业的第二个实例。

示例场景

  • 我有Job X,每1分钟从Quartz中获取文件并触发。
  • Job X通常可在1分钟内处理100个文件,超过100个文件将需要更长时间。
  • 自上次运行以来,有150个文件存在,因此Job X开始处理这些文件。当到达1分钟时,已处理100个文件,还剩下50个文件,并且Job X继续运行。
  • 然而,由于触发器每1分钟触发一次,第二个Job X实例被启动。
  • 现在有2个Job X实例正在获取相同的50个文件。

是否有一种方法可以将Quartz.NET连接到仅允许1个作业实例? 我可以让第二个触发器等待第一个完成或者我也可以让它跳过第二个触发器,因为它将在一分钟后再次触发。



我查看了Quartz API的Java版本,并找到了一个名为'DisallowConcurrentExecution'的属性,但在.NET版本中没有找到类似的属性。

我的Quartz.NET实现代码

public IScheduler Scheduler { get; set; }
public IJobListener AutofacJobListener { get; set; }

public void Start()
{
var trigger = TriggerUtils.MakeMinutelyTrigger(1);
trigger.Name = @"文档导入触发器";

Scheduler.ScheduleJob(new JobDetail("文档导入", null, typeof(DocumentImportJob)), trigger);
Scheduler.AddGlobalJobListener(AutofacJobListener);
Scheduler.Start();
}

1
如果上一个时间表仍在运行时,下一个时间表再次“滴答”应该发生什么?推迟还是跳过? - Lasse V. Karlsen
1
无论是跳过还是推迟,我都可以接受。 - Mark
1
你难道不能在程序运行时设置一个变量或其他东西吗?比如让任务“启动”,然后它立即退出。 - Lasse V. Karlsen
在您的函数内实现逻辑应该相对简单。 - CodesInChaos
我已按照您的建议尝试并标记为答案,但无法解决问题。请在 Github 上检查问题。我应该怎么做来解决这个问题?https://github.com/quartznet/quartznet/issues/469 - Jeeva J
4个回答

52

使用DisallowConcurrentExecution属性。

将您的类声明如下:

[DisallowConcurrentExecution]
public class SomeTask : IJob
{

} 

触发器的“misfire instruction”

如果一个持久性触发器错过了它的触发时间,可能是因为调度程序关闭或因为 Quartz.NET 的线程池中没有可用的线程来执行作业,那么就会发生“misfire”。不同类型的触发器都有不同的misfire指令可用。默认情况下,它们使用“智能策略”指令-它具有基于触发器类型和配置的动态行为。当调度程序启动时,它会搜索任何misfire的持久性触发器,并根据其各自配置的misfire指令更新它们。当您开始在自己的项目中使用Quartz.NET时,应该熟悉给定触发器类型上定义的misfire指令,并在其API文档中解释。每个触发器类型特定的教程课程将提供有关misfire instructions的更具体信息。

请查看这些页面底部的“触发器misfire instructions”信息:

Lesson 5: SimpleTrigger

Lesson 6: CronTrigger

旧的Quartz.NET API答案:

http://quartznet.sourceforge.net/apidoc/topic142.html:

IStatefulJob实例与常规IJob实例有稍微不同的规则。关键区别在于在每次作业执行后,它们关联的JobDataMap都会重新持久化,从而为下一次执行保留状态。另一个区别是,状态性作业不允许同时执行,这意味着在IJob.Execute方法完成之前发生的新触发器将被延迟。

因此,请将您的“Job”类声明如下:

class DocumentImportJob : IStatefulJob
{
   ......
} 
为避免任务在完成后重新触发(当任务需要超过1分钟并导致触发器“misfire”时),在创建触发器时执行以下操作(根据使用的触发器类型进行调整):
myJobTrigger.MisfireInstruction = MisfireInstruction.CronTrigger.DoNothing;  

https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/more-about-triggers.html


1
IStatefulJob已被弃用。 请在作业类定义上方使用[DisallowConcurrentExecution]。这是根据Quartz.NET 2.2.1的规定。 并且可以通过与触发器初始化一起使用以下方法来处理错过的触发器:WithMisfireHandlingInstructionIgnoreMisfires()。 - Khurram Ishaque

46

更新此答案,较新版本的Quartz.Net现在通过“DisallowConcurrentExecution”属性来实现,您可以将其应用于作业实现:

[DisallowConcurrentExecution]
public class MyJob : IJob  
{
    ..
}

对于误火指令,以下是如何操作的:

var trigger = TriggerBuilder.Create()
    .WithSimpleSchedule(ssb => ssb.WithIntervalInMinutes(interval)
        .RepeatForever()
        .WithMisfireHandlingInstructionIgnoreMisfires()
    )
    .Build();

4

一个简单的方法是在某个变量中存储一个标志值,在进入作业方法时检查该变量。

这样,您可以让作业“重新开始”,它只会立即退出而不执行任何真正的工作。

以下是一个示例:

private volatile bool _IsRunning;

...

if (Interlocked.Exchange(ref _IsRunning, true))
    return;
try
{
    // job code
}
finally
{
    _IsRunning = false;
}

你的回答很好,但我必须选择@Keith的回答,因为它正是我想要的,并且让Quartz处理一切。 - Mark
1
@Mark 是的,我也打算在他回答之前留下这样的评论,建议你暂时不要接受我的答案,因为 Quartz.Net 中可能有恰好符合你需求的功能,但是我不知道为什么错过了“添加评论”按钮。可能是我渴望声望的自我作祟 :) - Lasse V. Karlsen

-5

公共bool类型的Validar(IJobExecutionContext context)函数

        if (context.Scheduler.GetCurrentlyExecutingJobs().Any(x => x.FireInstanceId != context.FireInstanceId
         && x.JobDetail.Key == context.JobDetail.Key))
        {
            return true;
        }
        else
        {
            return false;
        }

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接