Quartz Microsoft 依赖注入集成包

Quartz.Extensions.DependencyInjection 提供了与 Microsoft 依赖注入 的集成。

提示 需要 Quartz 3.1 或更高版本。

安装

你需要在使用 Quartz 的项目中添加 NuGet 包引用。

Install-Package Quartz.Extensions.DependencyInjection

使用

你可以通过在 IServiceCollection 上调用扩展方法 AddQuartz 来添加 Quartz 配置。配置构建过程使用强类型 API 包含了各种 配置属性。你也可以在配置节 Quartz 内使用标准的 .NET Core appsettings.json 来配置属性。

提示 该节应像 此示例 所示,手动绑定到 QuartzOptions 类型,使用 AddOptionsConfigure

提示 Quartz.Extensions.Hosting 允许你为应用程序提供一个处理调度器启动和停止的后台服务。

示例 appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "Quartz": {
    "quartz.scheduler.instanceName": "Quartz ASP.NET Core 示例调度器"
  }
}

DI 感知的作业工厂

Quartz 内置了两种作业工厂的替代方案,可以通过调用 UseMicrosoftDependencyInjectionJobFactoryUseMicrosoftDependencyInjectionScopedJobFactory(已弃用)进行配置。

从 Quartz.NET 3.3.2 版本开始,由默认作业工厂产生的所有作业都是范围内的作业,因此你应该不再使用 UseMicrosoftDependencyInjectionJobFactoryUseMicrosoftDependencyInjectionScopedJobFactory

作业实例构造

默认情况下,Quartz 将尝试从容器解析作业类型,如果没有明确的注册,Quartz 将使用 ActivatorUtilities 构造作业并通过构造函数注入其依赖项。作业应只有一个公共构造函数。

持久化作业存储

调度配置将在每次应用程序启动并对调度进行评估时,检查数据库并相应更新。

警告
使用持久化作业存储时,请确保为你的调度配置作业和触发器的名称,以便正确地针对数据库中已有数据执行存在性检查。
使用 API 配置未显式配置作业标识的触发器和作业会在每次配置评估时导致作业和触发器生成不同的名称。
对于持久化作业存储,最佳实践是始终至少声明作业和触发器的名称。省略它们的组将为每次调用产生相同的默认组值。

示例 Startup.ConfigureServices 配置

public void ConfigureServices(IServiceCollection services)
{
    // 基础配置从 appsettings.json 获取
    services.ConfigureQuartzOptions<QuartzOptions>(Configuration.GetSection("Quartz"));

    // 如果使用持久化作业存储,你可能想要修改某些选项
    services.ConfigureQuartzOptions<QuartzOptions>(options =>
    {
        options.Scheduling.IgnoreDuplicates = true; // 默认:false
        options.Scheduling.OverWriteExistingData = true; // 默认:true
    });

    services.AddQuartz(q =>
    {
        // 当作为集群一部分或想以其他方式标识多个调度器时很有用
        q.SchedulerId = "Scheduler-Core";

        // 我们从 appsettings.json 获取这个,只是展示这样做的可能性
        // q.SchedulerName = "Quartz ASP.NET Core 示例调度器";

        // 这些是默认值
        q.UseSimpleTypeLoader();
        q.UseInMemoryStore();
        q.UseDefaultThreadPool(tp => { tp.MaxConcurrency = 10; });

        // 快速创建带单个触发器的作业的方法
        // (需要版本 3.2)
        q.ScheduleJob<ExampleJob>(trigger =>
            trigger
                .WithIdentity("组合配置触发器")
                .StartAt(DateBuilder.EvenSecondDate(DateTimeOffset.UtcNow.AddSeconds(7)))
                .WithDailyTimeIntervalSchedule(x => x.WithInterval(10, IntervalUnit.Second))
                .WithDescription("为单一调用配置的我的强大触发器"));

        // 你也可以用代码配置个别作业和触发器
        // 这允许你为同一个作业关联多个触发器
        // (例如,如果你希望每个触发器有不同的作业数据映射)
        q.AddJob<ExampleJob>(j => j
            .StoreDurably() // 如果没有关联触发器,我们需要持久存储
            .WithDescription("我的强大作业"));

        // 这里是一个已知的作业,用于触发器
        var jobKey = new JobKey("强大作业", "强大组");
        q.AddJob<ExampleJob>(jobKey, j => j
            .WithDescription("我的强大作业"));

        q.AddTrigger(t =>
            t
                .WithIdentity("简单触发器")
                .ForJob(jobKey)
                .StartNow()
                .WithSimpleSchedule(x => x.WithInterval(TimeSpan.FromSeconds(10)).RepeatForever())
                .WithDescription("我的强大简单触发器"));

        q.AddTrigger(t =>
            t
                .WithIdentity("Cron触发器")
                .ForJob(jobKey)
                .StartAt(DateBuilder.EvenSecondDate(DateTimeOffset.UtcNow.AddSeconds(3)))
                .WithCronSchedule("0/3 * * * * ?")
                .WithDescription("我的强大Cron触发器"));

        // 也可以添加日历(需要版本 3.2)
        const string calendarName = "我的假期日历";
        q.AddCalendar<HolidayCalendar>(
            name: calendarName,
            replace: true,
            updateTriggers: true,
            x => x.AddExcludedDate(new DateTime(2020, 5, 15))
        );

        q.AddTrigger(t => t
            .WithIdentity("Daily Trigger")
            .ForJob(jobKey)
            .StartAt(DateBuilder.EvenSecondDate(DateTimeOffset.UtcNow.AddSeconds(5)))
            .WithDailyTimeIntervalSchedule(x => x.WithInterval(10, IntervalUnit.Second))
            .WithDescription("my awesome daily time interval trigger")
            .ModifiedByCalendar(calendarName)
        );

        // 同样添加XML配置并轮询其变化
        q.UseXmlSchedulingConfiguration(x =>
        {
            x.Files = new[] { "~/quartz_jobs.config" };
            x.ScanInterval = TimeSpan.FromSeconds(2);
            x.FailOnFileNotFound = true;
            x.FailOnSchedulingError = true;
        });

        // 使用转换器转换时区,该转换器可以处理Windows/Linux差异
        q.UseTimeZoneConverter();

        // 自动中断长时间运行的作业
        q.UseJobAutoInterrupt(options =>
        {
            // 这是默认值
            options.DefaultMaxRunTime = TimeSpan.FromMinutes(5);
        });

        q.ScheduleJob<SlowJob>(triggerConfigurator =>
            triggerConfigurator
                .WithIdentity("慢速作业触发器")
                .StartNow()
                .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()),
            jobConfigurator =>
            jobConfigurator
                .WithIdentity("慢速作业")
                .UsingJobData(JobInterruptMonitorPlugin.JobDataMapKeyAutoInterruptable, true) // 允许此作业仅运行五秒,覆盖默认配置
                .UsingJobData(JobInterruptMonitorPlugin.JobDataMapKeyMaxRunTime, TimeSpan.FromSeconds(5).TotalMilliseconds.ToString(CultureInfo.InvariantCulture)));

        // 添加一些监听器
        q.AddSchedulerListener<SampleSchedulerListener>();
        q.AddJobListener<SampleJobListener>(GroupMatcher<JobKey>.GroupEquals(jobKey.Group));
        q.AddTriggerListener<SampleTriggerListener>();

        // 持久化作业存储的示例,使用JSON序列化器作为示例
        /*
        q.UsePersistentStore(s =>
        {
            s.PerformSchemaValidation = true; // 默认
            s.UseProperties = true; // 推荐,但非默认
            s.RetryInterval = TimeSpan.FromSeconds(15);
            s.UseSqlServer(sqlServer =>
            {
                sqlServer.ConnectionString = "某个连接字符串";
                // 这是默认值
                sqlServer.TablePrefix = "QRTZ_";
            });
            s.UseJsonSerializer();
            s.UseClustering(c =>
            {
                c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
                c.CheckinInterval = TimeSpan.FromSeconds(10);
            });
        }); */
    });

    // 我们可以使用选项模式来支持挂接你自己的配置
    // 因为我们没有使用服务注册API,
    // 我们需要手动确保作业存在于DI中
    services.AddTransient<ExampleJob>();

    services.Configure<SampleOptions>(Configuration.GetSection("Sample"));
    services.AddOptions<QuartzOptions>()
    .Configure<IOptions<SampleOptions>>((options, dep) =>
    {
    if (!string.IsNullOrWhiteSpace(dep.Value.CronSchedule))
    {
        var jobKey = new JobKey("options-custom-job", "custom");
        options.AddJob<ExampleJob>(j => j.WithIdentity(jobKey));
        options.AddTrigger(trigger => trigger
        .WithIdentity("options-custom-trigger", "custom")
        .ForJob(jobKey)
        .WithCronSchedule(dep.Value.CronSchedule));
    }
    });

    // Quartz.Extensions.Hosting 允许你启动管理调度器生命周期的后台服务
    services.AddQuartzHostedService(options =>
    {
        // 关闭时我们希望作业能优雅完成
        options.WaitForJobsToComplete = true;
    });
}
在本文档中