Quartz.NET 快速入门指南

欢迎来到 Quartz.NET 的快速入门指南。在阅读本指南的过程中,您将了解到以下内容:

  • 如何下载 Quartz.NET
  • 安装 Quartz.NET 的步骤
  • 根据您的特定需求配置 Quartz
  • 启动示例应用

下载与安装

您可以选择下载压缩文件或使用 NuGet 包。NuGet 包仅包含运行 Quartz.NET 所需的二进制文件,而压缩文件则包含了源代码、示例以及 Quartz.NET 服务器示例应用。

NuGet 包

没有比这更简单的了。只需启动已安装 NuGet 的 Visual Studio,然后从包管理器扩展中引用 Quartz 包:

  • 右键点击项目中的“引用”,选择 管理 NuGet 包...
  • 从左侧选择 在线 分类
  • 在右上角搜索框输入 Quartz 并回车
  • 从搜索结果中选择 Quartz.NET 并点击安装
  • 完成!

或使用 NuGet 命令行工具:

Install-Package Quartz

如果您希望添加 JSON 序列化支持,同样方式添加 Quartz.Serialization.Json 包。

压缩包下载

简短版 :下载 Quartz.NET 后,解压到任意位置,从 bin 目录中取出 Quartz.dll 文件并开始使用它。

Quartz 核心库没有硬性的二进制依赖。当您选择使用 JSON 序列化包时,才需要 JSON.NET。为了让 Quartz.NET 成功运行,至少需要将 Quartz.dll 放在应用二进制文件旁边。因此,只需将其作为引用添加到使用它们的 Visual Studio 项目中。您可以在解压后的档案中,通过路径 bin\your-target-framework-version\release\Quartz 找到这些 dll 文件。

配置

这是关键环节!Quartz.NET 是一个高度可配置的库。提供 Quartz.NET 配置信息主要有两种方式(这两种方式并不互斥):

流畅调度器构建器 API

您可以使用 C# 的流畅 API 来配置调度器,或者通过向调度器工厂提供包含配置键值对的 NameValueCollection 参数来进行配置。

// 初始化一个基础属性集合,这里未展示具体添加属性的代码,但你可以在此处添加自定义的配置项
var properties = new NameValueCollection();

// 使用SchedulerBuilder创建调度器实例,并根据需要覆盖或添加配置
IScheduler scheduler = await SchedulerBuilder.Create(properties)
    // 默认最大并发度为10,这里修改为5
    .UseDefaultThreadPool(x => x.MaxConcurrency = 5)
    // 以下注释掉的行表示默认情况下,作业错过触发时间的阈值为60秒,可以根据需要取消注释并调整
    // .WithMisfireThreshold(TimeSpan.FromSeconds(60))
    // 配置持久化存储策略
    .UsePersistentStore(x =>
    {
        // 强制作业数据映射的值被视为字符串,避免对象意外序列化后格式破坏导致的问题,默认为false
        x.UseProperties = true;
        // 启用集群模式
        x.UseClustering();
        // 使用SQL Server作为持久化存储的提供者,并提供连接字符串
        x.UseSqlServer("my connection string");
        // 使用JSON序列化器,要求添加Quartz.Serialization.Json NuGet包
        x.UseJsonSerializer();
    })
    // 配置XML作业初始化插件来处理XML作业定义文件,需要Quartz.Plugins NuGet包
    .UseXmlSchedulingConfiguration(x =>
    {
        // 指定XML作业定义文件的位置,这里是相对路径
        x.Files = new[] { "~/quartz_jobs.xml" };
        // 如果找不到定义文件,默认行为是失败(此处保持默认)
        x.FailOnFileNotFound = true;
        // 当遇到作业调度错误时,默认不失败,这里修改为遇到错误时失败
        x.FailOnSchedulingError = true;
    })
    // 最后,基于上述配置构建并返回调度器实例
    .BuildScheduler();
// 启动调度器
await scheduler.Start();

配置文件

以下文件会被搜索以查找已知的配置属性:

  • YourApplication.exe.config - 使用 quartz 元素的配置文件(仅限完整 .NET 框架)
  • appsettings.json - (.NET Core/NET5 及以后版本)
  • quartz.config - 应用程序根目录下的配置文件(同时适用于 .NET Core 和完整 .NET 框架)

所有可用属性的完整文档参见 Quartz 配置参考

为了快速上手,一个基本的 quartz.config 文件内容大致如下:

quartz.scheduler.instanceName = MyScheduler
quartz.jobStore.type = Quartz.Simpl.RAMJobStore, Quartz
quartz.threadPool.maxConcurrency = 3

请记得在 Visual Studio 的文件属性页面中设置 复制到输出目录始终复制。否则,如果配置文件不在构建目录中,它将不会被识别到。

通过此配置创建的调度器具有以下特点:

  • quartz.scheduler.instanceName - 此调度器的名称将为 "MyScheduler"。
  • quartz.threadPool.maxConcurrency - 最多可同时运行 3 个作业(默认为 10)。
  • quartz.jobStore.type - Quartz 的所有数据(如作业和触发器的详细信息)都保存在内存中(而不是数据库中)。
  • 即使你有数据库并希望将其与 Quartz 一起使用,我建议你在开始处理数据库所带来的全新维度之前,先确保 Quartz 能够使用 RamJobStore 正常工作。

提示 实际上,如果你不想定义这些属性也是可以的,Quartz.NET 自带了合理的默认值。

启动示例应用程序

现在你已经下载并安装了 Quartz,是时候让一个示例应用程序跑起来了。下面的代码将获取调度器的一个实例,启动它,然后关闭它:

Program.cs

using System;
using System.Threading.Tasks;

using Quartz;
using Quartz.Impl;

namespace QuartzSampleApp
{
    public class Program
    {
        private static async Task Main(string[] args)
        {
            // Grab the Scheduler instance from the Factory
            StdSchedulerFactory factory = new StdSchedulerFactory();
            IScheduler scheduler = await factory.GetScheduler();

            // and start it off
            await scheduler.Start();

            // some sleep to show what's happening
            await Task.Delay(TimeSpan.FromSeconds(10));

            // and last shut down the scheduler when you are ready to close your program
            await scheduler.Shutdown();
        }
    }
}

自 Quartz 3.0 起,当你在调用了 scheduler.Shutdown() 后没有其他代码需要执行时,你的应用程序将终止,因为那时将不再有任何活动线程。如果你想让调度器在 Task.DelayShutdown 处理完毕后继续保持运行,你应该手动阻止应用程序退出。

现在运行程序不会显示任何信息。当过了 10 秒后,程序就会直接终止。让我们向控制台添加一些日志信息来观察程序运行情况。

添加日志功能

LibLog(在新窗口中打开) 可以配置为在后台使用不同的日志框架,包括 Log4Net、NLog 和 Serilog。如果 LibLog 没有检测到任何其他日志框架存在,它将保持静默。我们可以配置一个简单的日志提供程序,仅将日志输出到控制台,以便在尚未准备好日志框架设置时查看输出。

LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());

private class ConsoleLogProvider : ILogProvider
{
    public Logger GetLogger(string name)
    {
        return (level, func, exception, parameters) =>
        {
            if (level >= LogLevel.Info && func != null)
            {
                Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters);
            }
            return true;
        };
    }

    public IDisposable OpenNestedContext(string message)
    {
        throw new NotImplementedException();
    }

    public IDisposable OpenMappedContext(string key, object value, bool destructure = false)
    {
        throw new NotImplementedException();
    }
}

测试应用程序

现在启动应用程序时,我们应该能获得更多信息。

[12.51.10] [Info] Quartz.NET properties loaded from configuration file 'C:\QuartzSampleApp\quartz.config'
[12.51.10] [Info] Initialized Scheduler Signaller of type: Quartz.Core.SchedulerSignalerImpl
[12.51.10] [Info] Quartz Scheduler created
[12.51.10] [Info] RAMJobStore initialized.
[12.51.10] [Info] Scheduler meta-data: Quartz Scheduler (v3.0.0.0) 'MyScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'Quartz.Core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'Quartz.Simpl.DefaultThreadPool' - with 3 threads.
  Using job-store 'Quartz.Simpl.RAMJobStore' - which does not support persistence. and is not clustered.

[12.51.10] [Info] Quartz scheduler 'MyScheduler' initialized
[12.51.10] [Info] Quartz scheduler version: 3.0.0.0
[12.51.10] [Info] Scheduler MyScheduler_$_NON_CLUSTERED started.

我们需要一个简单的测试任务来验证功能,创建一个名为 HelloJob 的任务,它会向控制台输出问候语。

public class HelloJob : IJob
{
 public async Task Execute(IJobExecutionContext context)
 {
  await Console.Out.WriteLineAsync("Greetings from HelloJob!");
 }
}

为了做些有趣的事情,你需要在 Start() 方法之后,Task.Delay 之前添加一些代码。

// define the job and tie it to our HelloJob class
IJobDetail job = JobBuilder.Create<HelloJob>()
 .WithIdentity("job1", "group1")
 .Build();

// Trigger the job to run now, and then repeat every 10 seconds
ITrigger trigger = TriggerBuilder.Create()
 .WithIdentity("trigger1", "group1")
 .StartNow()
 .WithSimpleSchedule(x => x
  .WithIntervalInSeconds(10)
  .RepeatForever())
 .Build();

// Tell Quartz to schedule the job using our trigger
await scheduler.ScheduleJob(job, trigger);

// You could also schedule multiple triggers for the same job with
// await scheduler.ScheduleJob(job, new List<ITrigger>() { trigger1, trigger2 }, replace: true);

完整的控制台应用程序现在看起来应该是这样的:

using System;
using System.Threading.Tasks;

using Quartz;
using Quartz.Impl;
using Quartz.Logging;

namespace QuartzSampleApp
{
    public class Program
    {
        private static async Task Main(string[] args)
        {
            LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());

            // Grab the Scheduler instance from the Factory
            StdSchedulerFactory factory = new StdSchedulerFactory();
            IScheduler scheduler = await factory.GetScheduler();

            // and start it off
            await scheduler.Start();

            // define the job and tie it to our HelloJob class
            IJobDetail job = JobBuilder.Create<HelloJob>()
                .WithIdentity("job1", "group1")
                .Build();

            // Trigger the job to run now, and then repeat every 10 seconds
            ITrigger trigger = TriggerBuilder.Create()
                .WithIdentity("trigger1", "group1")
                .StartNow()
                .WithSimpleSchedule(x => x
                    .WithIntervalInSeconds(10)
                    .RepeatForever())
                .Build();

            // Tell Quartz to schedule the job using our trigger
            await scheduler.ScheduleJob(job, trigger);

            // some sleep to show what's happening
            await Task.Delay(TimeSpan.FromSeconds(60));

            // and last shut down the scheduler when you are ready to close your program
            await scheduler.Shutdown();

            Console.WriteLine("Press any key to close the application");
            Console.ReadKey();
        }

        // simple log provider to get something to the console
        private class ConsoleLogProvider : ILogProvider
        {
            public Logger GetLogger(string name)
            {
                return (level, func, exception, parameters) =>
                {
                    if (level >= LogLevel.Info && func != null)
                    {
                        Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters);
                    }
                    return true;
                };
            }

            public IDisposable OpenNestedContext(string message)
            {
                throw new NotImplementedException();
            }

            public IDisposable OpenMappedContext(string key, object value, bool destructure = false)
            {
                throw new NotImplementedException();
            }
        }
    }

    public class HelloJob : IJob
    {
        public async Task Execute(IJobExecutionContext context)
        {
            await Console.Out.WriteLineAsync("Greetings from HelloJob!");
        }
    }
}

创建并初始化数据库

为了使用 SQL 持久化存储来支持 Quartz,并启用如集群等功能,你需要创建一个数据库并使用 SQL 脚本初始化模式对象。首先,你需要为 Quartz 创建一个数据库及凭证。在 Quartz 能够连接到的数据库准备好之后,你还需创建 Quartz 运行所需的数据表和索引。

最新的 DDL 脚本可以在 Quartz 的 GitHub 仓库(在新窗口中打开) 中找到,它们也包含在 ZIP 归档分发包中。还有第三方对 Quartz 的扩展,使得其能支持其他类型的存储,比如 NoSQL 数据库。你可以在 NuGet 上搜索它们。

现在,你可以开始探索 Quartz.NET 并享受乐趣了!你可以继续阅读 教程 深入学习。

在本文档中