# 任务调度 (11_Quartz)
最后更新: 2024-09-20
# 📚 概述
DarkM 任务调度模块集成了 Quartz.NET 任务调度框架,提供了统一的定时任务管理功能,支持 Cron 表达式、任务持久化、任务日志等功能。
源代码位置: DarkM/src/Framework/Quartz
# 🏗️ 模块架构
# 完整目录结构
Quartz/
├── Quartz.Abstractions/
│ ├── IQuartzServer.cs # Quartz 服务器接口
│ ├── ITask.cs # 任务接口
│ ├── TaskAbstract.cs # 任务抽象基类
│ ├── ITaskExecutionContext.cs # 任务执行上下文接口
│ ├── ITaskLogger.cs # 任务日志记录器接口
│ ├── QuartzTaskDescriptor.cs # Quartz 任务描述符
│ ├── QuartzConfig.cs # Quartz 配置
│ ├── QuartzProvider.cs # Quartz 提供器枚举
│ └── QuartzModuleDescriptor.cs # Quartz 模块描述符
│
└── Quartz.Core/
├── QuartzServer.cs # Quartz 服务器实现
├── JobFactory.cs # 任务工厂
├── TaskLogger.cs # 任务日志记录器
├── QuartzModuleCollection.cs # Quartz 模块集合
├── QuartzAppShutdownHandler.cs # Quartz 应用关闭处理器
└── ServiceCollectionExtensions.cs # DI 扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 💡 配置说明
# appsettings.json 配置
{
"Quartz": {
// 提供器:0=RAMJobStore, 1=ADO.NET
"Provider": 1,
// 数据库连接字符串(ADO.NET 模式)
"ConnectionString": "",
// 表前缀
"TablePrefix": "QRTZ_",
// 实例名称
"InstanceName": "DarkM_Quartz",
// 是否开启 Web 控制台
"Dashboard": true,
// 控制台端口
"DashboardPort": 5000
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 配置项详解
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| Provider | QuartzProvider | 1 | 任务存储提供器 |
| ConnectionString | string | - | 数据库连接字符串 |
| TablePrefix | string | QRTZ_ | Quartz 表前缀 |
| InstanceName | string | DarkM_Quartz | Quartz 实例名称 |
| Dashboard | bool | true | 是否启用 Web 控制台 |
| DashboardPort | int | 5000 | Web 控制台端口 |
# 🔧 核心接口
# 1. IQuartzServer(Quartz 服务器)
文件位置: Quartz.Abstractions/IQuartzServer.cs
public interface IQuartzServer
{
/// <summary>
/// 启动服务器
/// </summary>
Task Start();
/// <summary>
/// 停止服务器
/// </summary>
Task Stop();
/// <summary>
/// 暂停所有任务
/// </summary>
Task PauseAll();
/// <summary>
/// 恢复所有任务
/// </summary>
Task ResumeAll();
/// <summary>
/// 获取所有任务
/// </summary>
Task<IList<QuartzTaskDescriptor>> GetAllTasks();
/// <summary>
/// 添加任务
/// </summary>
Task AddJob(QuartzTaskDescriptor task);
/// <summary>
/// 删除任务
/// </summary>
Task DeleteJob(string jobName, string jobGroup);
/// <summary>
/// 暂停任务
/// </summary>
Task PauseJob(string jobName, string jobGroup);
/// <summary>
/// 恢复任务
/// </summary>
Task ResumeJob(string jobName, string jobGroup);
/// <summary>
/// 立即执行任务
/// </summary>
Task TriggerJob(string jobName, string jobGroup);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 2. ITask(任务接口)
文件位置: Quartz.Abstractions/ITask.cs
public interface ITask
{
/// <summary>
/// 任务名称
/// </summary>
string Name { get; }
/// <summary>
/// 任务分组
/// </summary>
string Group { get; }
/// <summary>
/// Cron 表达式
/// </summary>
string Cron { get; }
/// <summary>
/// 任务描述
/// </summary>
string Description { get; }
/// <summary>
/// 是否启用
/// </summary>
bool Enabled { get; }
/// <summary>
/// 执行任务
/// </summary>
Task Execute(ITaskExecutionContext context);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 3. TaskAbstract(任务抽象基类)
文件位置: Quartz.Abstractions/TaskAbstract.cs
public abstract class TaskAbstract : ITask
{
/// <summary>
/// 任务名称
/// </summary>
public abstract string Name { get; }
/// <summary>
/// 任务分组
/// </summary>
public abstract string Group { get; }
/// <summary>
/// Cron 表达式
/// </summary>
public abstract string Cron { get; }
/// <summary>
/// 任务描述
/// </summary>
public virtual string Description => "";
/// <summary>
/// 是否启用
/// </summary>
public virtual bool Enabled => true;
/// <summary>
/// 执行任务
/// </summary>
public abstract Task Execute(ITaskExecutionContext context);
/// <summary>
/// 任务开始前调用
/// </summary>
protected virtual Task OnExecuting() => Task.CompletedTask;
/// <summary>
/// 任务执行完成后调用
/// </summary>
protected virtual Task OnExecuted() => Task.CompletedTask;
/// <summary>
/// 任务执行失败时调用
/// </summary>
protected virtual Task OnError(Exception ex) => Task.CompletedTask;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 4. ITaskExecutionContext(任务执行上下文)
文件位置: Quartz.Abstractions/ITaskExecutionContext.cs
public interface ITaskExecutionContext
{
/// <summary>
/// 任务名称
/// </summary>
string JobName { get; }
/// <summary>
/// 任务分组
/// </summary>
string JobGroup { get; }
/// <summary>
/// 触发器名称
/// </summary>
string TriggerName { get; }
/// <summary>
/// 触发器分组
/// </summary>
string TriggerGroup { get; }
/// <summary>
/// 任务数据
/// </summary>
JobDataMap JobDataMap { get; }
/// <summary>
/// 获取日志记录器
/// </summary>
ITaskLogger Logger { get; }
/// <summary>
/// 取消令牌
/// </summary>
CancellationToken CancellationToken { get; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 5. ITaskLogger(任务日志记录器)
文件位置: Quartz.Abstractions/ITaskLogger.cs
public interface ITaskLogger
{
/// <summary>
/// 记录调试日志
/// </summary>
void Debug(string message);
/// <summary>
/// 记录信息日志
/// </summary>
void Info(string message);
/// <summary>
/// 记录警告日志
/// </summary>
void Warn(string message);
/// <summary>
/// 记录错误日志
/// </summary>
void Error(string message, Exception ex = null);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 💡 使用示例
# 1. 简单任务
文件位置: Module.Quartz/Jobs/HelloTask.cs
using DarkM.Lib.Quartz.Abstractions;
namespace DarkM.Module.Quartz.Jobs
{
/// <summary>
/// Hello World 示例任务
/// </summary>
public class HelloTask : TaskAbstract
{
public override string Name => "HelloTask";
public override string Group => "Demo";
public override string Cron => "0/10 * * * * ?"; // 每 10 秒执行一次
public override string Description => "Hello World 示例任务";
public override async Task Execute(ITaskExecutionContext context)
{
context.Logger.Info("Hello World!");
await Task.Delay(1000);
context.Logger.Info("任务执行完成");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 2. 带参数的任务
using DarkM.Lib.Quartz.Abstractions;
namespace DarkM.Module.Quartz.Jobs
{
/// <summary>
/// 数据同步任务
/// </summary>
public class DataSyncTask : TaskAbstract
{
private readonly IDataService _dataService;
public DataSyncTask(IDataService dataService)
{
_dataService = dataService;
}
public override string Name => "DataSyncTask";
public override string Group => "System";
public override string Cron => "0 0 2 * * ?"; // 每天凌晨 2 点执行
public override string Description => "数据同步任务";
protected override async Task OnExecuting()
{
Logger.Info("开始数据同步...");
}
public override async Task Execute(ITaskExecutionContext context)
{
try
{
// 从 JobDataMap 获取参数
var sourceDb = context.JobDataMap.GetString("SourceDb");
var targetDb = context.JobDataMap.GetString("TargetDb");
Logger.Info($"从 {sourceDb} 同步到 {targetDb}");
await _dataService.SyncAsync(sourceDb, targetDb);
Logger.Info("数据同步完成");
}
catch (Exception ex)
{
Logger.Error("数据同步失败", ex);
throw;
}
}
protected override async Task OnError(Exception ex)
{
Logger.Error("任务执行失败", ex);
// 发送告警通知
await SendAlertAsync(ex);
}
private Task SendAlertAsync(Exception ex)
{
// 发送告警逻辑
return Task.CompletedTask;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 3. 数据库清理任务
using DarkM.Lib.Quartz.Abstractions;
using DarkM.Lib.Data.Abstractions;
namespace DarkM.Module.Quartz.Jobs
{
/// <summary>
/// 清理日志任务
/// </summary>
public class CleanLogTask : TaskAbstract
{
private readonly IDbSet<LogEntity> _logDbSet;
public CleanLogTask(IDbSet<LogEntity> logDbSet)
{
_logDbSet = logDbSet;
}
public override string Name => "CleanLogTask";
public override string Group => "System";
public override string Cron => "0 0 3 * * ?"; // 每天凌晨 3 点执行
public override string Description => "清理日志数据(保留 30 天)";
public override async Task Execute(ITaskExecutionContext context)
{
var keepDays = 30;
var cutoffDate = DateTime.Now.AddDays(-keepDays);
Logger.Info($"开始清理{keepDays}天前的日志数据...");
var count = await _logDbSet.Find(e => e.CreateTime < cutoffDate)
.ExecuteDeleteAsync();
Logger.Info($"清理完成,共删除 {count} 条记录");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 🔧 任务配置
# 方式 1:代码配置
在模块的 ModuleDescriptor 中注册任务:
using DarkM.Lib.Quartz.Abstractions;
public class QuartzModuleDescriptor : QuartzModuleDescriptorAbstract
{
public override void ConfigureServices(IServiceCollection services, IModuleCollection modules)
{
// 注册任务
services.AddSingleton<ITask, HelloTask>();
services.AddSingleton<ITask, DataSyncTask>();
services.AddSingleton<ITask, CleanLogTask>();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 方式 2:数据库配置
任务信息保存在数据库的 Quartz_Job 表中:
INSERT INTO Quartz_Job (Name, [Group], Cron, Description, Enabled, Assembly, Class)
VALUES
('HelloTask', 'Demo', '0/10 * * * * ?', 'Hello World 示例任务', 1, 'DarkM.Module.Quartz', 'DarkM.Module.Quartz.Jobs.HelloTask'),
('DataSyncTask', 'System', '0 0 2 * * ?', '数据同步任务', 1, 'DarkM.Module.Quartz', 'DarkM.Module.Quartz.Jobs.DataSyncTask');
1
2
3
4
2
3
4
# 🔧 Cron 表达式
# 格式
秒 分 时 日 月 周 年(可选)
1
# 特殊字符
| 字符 | 说明 | 示例 |
|---|---|---|
| * | 任意值 | * * * * * ? 每秒执行 |
| ? | 不指定值 | 0 0 12 ? * MON 每周一中午 12 点 |
| - | 范围 | 0 0 12-18 * * ? 每天 12-18 点每小时执行 |
| , | 枚举值 | 0 0 12,18 * * ? 每天 12 点和 18 点执行 |
| / | 增量 | 0 0/10 * * * ? 每 10 分钟执行 |
| L | 最后 | 0 0 12 L * ? 每月最后一天中午 12 点 |
| W | 最近工作日 | 0 0 12 15W * ? 每月 15 日最近的工作日 |
| # | 第几个周 X | 0 0 12 ? * 6#3 每月第 3 个周五 |
# 常用示例
| 表达式 | 说明 |
|---|---|
0/10 * * * * ? | 每 10 秒执行 |
0 0/5 * * * ? | 每 5 分钟执行 |
0 0 * * * ? | 每小时执行 |
0 0 8-18 * * ? | 每天 8-18 点每小时执行 |
0 0 2 * * ? | 每天凌晨 2 点执行 |
0 0 2 ? * MON | 每周一凌晨 2 点执行 |
0 0 2 1 * ? | 每月 1 日凌晨 2 点执行 |
0 0 2 * * MON-FRI | 每周一至周五凌晨 2 点执行 |
0 0 12 L * ? | 每月最后一天中午 12 点执行 |
0 0 0 1 1 ? | 每年 1 月 1 日零点执行 |
# 🔧 任务管理
# 1. 启动/停止任务
// 注入 IQuartzServer
private readonly IQuartzServer _quartzServer;
// 启动服务器
await _quartzServer.Start();
// 停止服务器
await _quartzServer.Stop();
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2. 添加任务
await _quartzServer.AddJob(new QuartzTaskDescriptor
{
Name = "HelloTask",
Group = "Demo",
Cron = "0/10 * * * * ?",
Description = "Hello World 示例任务",
Enabled = true,
Assembly = "DarkM.Module.Quartz",
Class = "DarkM.Module.Quartz.Jobs.HelloTask"
});
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 3. 删除任务
await _quartzServer.DeleteJob("HelloTask", "Demo");
1
# 4. 暂停/恢复任务
// 暂停任务
await _quartzServer.PauseJob("HelloTask", "Demo");
// 恢复任务
await _quartzServer.ResumeJob("HelloTask", "Demo");
1
2
3
4
5
2
3
4
5
# 5. 立即执行任务
await _quartzServer.TriggerJob("HelloTask", "Demo");
1
# 6. 获取所有任务
var tasks = await _quartzServer.GetAllTasks();
foreach (var task in tasks)
{
Console.WriteLine($"{task.Name} ({task.Group}): {task.Cron}");
}
1
2
3
4
5
6
2
3
4
5
6
# 🖥️ Web 控制台
Quartz 提供了 Web 控制台,可以查看和管理任务。
# 启用控制台
appsettings.json:
{
"Quartz": {
"Dashboard": true,
"DashboardPort": 5000
}
}
1
2
3
4
5
6
2
3
4
5
6
# 访问控制台
启动后访问:http://localhost:5000
# 控制台功能
- ✅ 查看所有任务列表
- ✅ 查看任务执行历史
- ✅ 手动触发任务
- ✅ 暂停/恢复任务
- ✅ 添加/删除任务
- ✅ 查看任务日志
# 📚 最佳实践
# 1. 使用依赖注入
任务类通过构造函数注入所需服务:
public class DataSyncTask : TaskAbstract
{
private readonly IDataService _dataService;
private readonly ILogger _logger;
public DataSyncTask(IDataService dataService, ILogger logger)
{
_dataService = dataService;
_logger = logger;
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 2. 处理异常
在 Execute 方法中捕获并记录异常:
public override async Task Execute(ITaskExecutionContext context)
{
try
{
// 任务逻辑
await DoWorkAsync();
}
catch (Exception ex)
{
context.Logger.Error("任务执行失败", ex);
throw; // 重新抛出,让 Quartz 记录失败
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 3. 使用生命周期方法
利用 OnExecuting/OnExecuted/OnError:
protected override async Task OnExecuting()
{
Logger.Info("任务开始执行");
}
protected override async Task OnExecuted()
{
Logger.Info("任务执行完成");
}
protected override async Task OnError(Exception ex)
{
Logger.Error("任务执行失败", ex);
await SendAlertAsync(ex);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4. 避免长时间运行的任务
如果任务执行时间较长,考虑:
- 使用异步方法
- 设置合理的超时时间
- 考虑拆分为多个小任务
# 5. 任务幂等性
确保任务可以重复执行:
public override async Task Execute(ITaskExecutionContext context)
{
// 检查是否已经在执行
if (await IsRunningAsync())
{
Logger.Warn("任务已在执行,跳过");
return;
}
try
{
await DoWorkAsync();
}
finally
{
// 清理状态
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 🔍 常见问题
# Q1: 任务不执行?
检查清单:
- ✅ Cron 表达式是否正确
- ✅ 任务是否启用(Enabled = true)
- ✅ Quartz 服务器是否启动
- ✅ 任务类是否正确注册
解决方案:
// 检查任务状态
var tasks = await _quartzServer.GetAllTasks();
var task = tasks.FirstOrDefault(t => t.Name == "HelloTask");
if (task == null)
{
Logger.Error("任务未注册");
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# Q2: 任务重复执行?
检查:
- ✅ 是否启动了多个 Quartz 实例
- ✅ 是否使用了集群模式
- ✅ 检查
InstanceId配置
解决方案:
// 使用集群模式
{
"Quartz": {
"InstanceName": "DarkM_Quartz",
"InstanceId": "AUTO",
"Clustered": true
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# Q3: 任务执行时间不准确?
解决方案:
Quartz 默认使用简单触发器,如果需要精确时间,使用 Cron 触发器:
// ✅ 推荐:Cron 触发器
public override string Cron => "0 0 2 * * ?";
// ❌ 不推荐:简单触发器
public override int Interval => 3600;
1
2
3
4
5
2
3
4
5
# Q4: 任务执行失败?
查看:
- ✅ 任务日志
- ✅ Quartz 执行历史
- ✅ 数据库中的错误信息
解决方案:
// 启用详细日志
{
"Quartz": {
"Logging": true,
"LogLevel": "Debug"
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# Q5: 如何停止正在执行的任务?
解决方案:
- 使用取消令牌
public override async Task Execute(ITaskExecutionContext context)
{
try
{
await DoWorkAsync(context.CancellationToken);
}
catch (OperationCanceledException)
{
Logger.Warn("任务被取消");
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 暂停任务
await _quartzServer.PauseJob("HelloTask", "Demo");
1
# 📚 相关文档
# 🔗 参考链接
- 源代码 (opens new window) -
src/Framework/Quartz - Quartz.NET 官方文档 (opens new window)
- Quartz.NET GitHub (opens new window)
最后更新: 2024-09-20