Quartz.NET Scheduled Tasks
5/15/24About 3 min
Quartz.NET Scheduled Tasks
Overview
Quartz.NET is a powerful open-source job scheduling framework for implementing scheduled tasks in .NET applications.
1. Installation and Configuration
1.1 Install Dependencies
dotnet add package Quartz
dotnet add package Quartz.Extensions.DependencyInjection1.2 Basic Configuration
// Program.cs
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
var builder = WebApplication.CreateBuilder(args);
// Add Quartz services
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
// Add job
q.AddJob<HelloWorldJob>(j => j.WithIdentity("HelloWorldJob"));
// Add trigger
q.AddTrigger(t => t
.WithIdentity("HelloWorldTrigger")
.ForJob("HelloWorldJob")
.WithCronSchedule("0/5 * * * * ?")); // Run every 5 seconds
});
// Add Quartz hosted service
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
var app = builder.Build();
app.Run();2. Job Implementation
2.1 Create a Job Class
using Quartz;
public class HelloWorldJob : IJob
{
private readonly ILogger<HelloWorldJob> _logger;
public HelloWorldJob(ILogger<HelloWorldJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Hello World! 任务执行时间: {Time}", DateTime.Now);
return Task.CompletedTask;
}
}2.2 Jobs with Data
public class DataJob : IJob
{
private readonly ILogger<DataJob> _logger;
public DataJob(ILogger<DataJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
// Get job data
var jobData = context.JobDetail.JobDataMap;
var message = jobData.GetString("Message");
var count = jobData.GetInt("Count");
_logger.LogInformation("收到消息: {Message}, 计数: {Count}", message, count);
return Task.CompletedTask;
}
}3. Trigger Types
3.1 SimpleTrigger
// Simple trigger - fixed interval execution
var trigger = TriggerBuilder.Create()
.WithIdentity("SimpleTrigger")
.StartNow()
.WithSimpleSchedule(s => s
.WithIntervalInSeconds(10) // Run every 10 seconds
.RepeatForever()) // Repeat indefinitely
.Build();3.2 CronTrigger
// Cron trigger - based on Cron expression
var trigger = TriggerBuilder.Create()
.WithIdentity("CronTrigger")
.WithCronSchedule("0 0 2 * * ?") // Run daily at 2:00 AM
.Build();3.3 Cron Expression Examples
| Expression | Description |
|---|---|
0 0 2 * * ? | Daily at 2:00 AM |
0 30 10 * * ? | Daily at 10:30 AM |
0 0/5 * * * ? | Every 5 minutes |
0 0 12 * * MON-FRI | Weekdays at 12:00 PM |
0 0 1 1 * ? | 1:00 AM on the 1st of each month |
0 0 0 L * ? | Last day of each month at midnight |
4. Job Storage
4.1 In-Memory Storage (Default)
// In-memory storage - data is stored in memory and lost after restart
builder.Services.AddQuartz(q =>
{
q.UseInMemoryStore();
});4.2 SQL Server Storage
dotnet add package Quartz.Plugins.RecentHistory
dotnet add package Microsoft.Data.SqlClientbuilder.Services.AddQuartz(q =>
{
q.UsePersistentStore(s =>
{
s.UseSqlServer("Server=localhost;Database=Quartz;Trusted_Connection=True;");
s.UseJsonSerializer();
s.UseClusteredStore(); // Cluster mode
});
});4.3 Create Database Tables
Quartz.NET provides database initialization scripts available on GitHub:
5. Job Management
5.1 Scheduler API
public class JobScheduler
{
private readonly IScheduler _scheduler;
public JobScheduler(ISchedulerFactory schedulerFactory)
{
_scheduler = schedulerFactory.GetScheduler().Result;
}
// Add job
public async Task ScheduleJob<T>(string jobName, string cronExpression) where T : IJob
{
var job = JobBuilder.Create<T>()
.WithIdentity(jobName)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"{jobName}Trigger")
.ForJob(job)
.WithCronSchedule(cronExpression)
.Build();
await _scheduler.ScheduleJob(job, trigger);
}
// Pause job
public async Task PauseJob(string jobName)
{
await _scheduler.PauseJob(new JobKey(jobName));
}
// Resume job
public async Task ResumeJob(string jobName)
{
await _scheduler.ResumeJob(new JobKey(jobName));
}
// Delete job
public async Task DeleteJob(string jobName)
{
await _scheduler.DeleteJob(new JobKey(jobName));
}
}5.2 Dynamically Add Jobs
// Dynamically add a job at runtime
var scheduler = await _schedulerFactory.GetScheduler();
var jobData = new JobDataMap();
jobData.Put("Message", "动态添加的消息");
var job = JobBuilder.Create<DataJob>()
.WithIdentity("DynamicJob")
.UsingJobData(jobData)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("DynamicTrigger")
.ForJob(job)
.StartNow()
.WithSimpleSchedule(s => s
.WithIntervalInMinutes(5)
.RepeatForever())
.Build();
await scheduler.ScheduleJob(job, trigger);6. Advanced Features
6.1 Job Listener
public class JobListener : IJobListener
{
private readonly ILogger<JobListener> _logger;
public JobListener(ILogger<JobListener> logger)
{
_logger = logger;
}
public string Name => "JobListener";
public Task JobToBeExecuted(IJobExecutionContext context)
{
_logger.LogInformation("作业即将执行: {JobName}", context.JobDetail.Key.Name);
return Task.CompletedTask;
}
public Task JobExecutionVetoed(IJobExecutionContext context)
{
_logger.LogInformation("作业执行被否决: {JobName}", context.JobDetail.Key.Name);
return Task.CompletedTask;
}
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
if (jobException != null)
{
_logger.LogError(jobException, "作业执行失败: {JobName}", context.JobDetail.Key.Name);
}
else
{
_logger.LogInformation("作业执行完成: {JobName}", context.JobDetail.Key.Name);
}
return Task.CompletedTask;
}
}6.2 Trigger Listener
public class TriggerListener : ITriggerListener
{
private readonly ILogger<TriggerListener> _logger;
public TriggerListener(ILogger<TriggerListener> logger)
{
_logger = logger;
}
public string Name => "TriggerListener";
public Task TriggerFired(ITrigger trigger, IJobExecutionContext context)
{
_logger.LogInformation("触发器触发: {TriggerName}", trigger.Key.Name);
return Task.CompletedTask;
}
public Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context)
{
return Task.FromResult(false); // Return true to veto execution
}
public Task TriggerMisfired(ITrigger trigger)
{
_logger.LogWarning("触发器错过执行: {TriggerName}", trigger.Key.Name);
return Task.CompletedTask;
}
public Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode)
{
_logger.LogInformation("触发器完成: {TriggerName}", trigger.Key.Name);
return Task.CompletedTask;
}
}6.3 Configure Listeners
builder.Services.AddQuartz(q =>
{
// Add job listener
q.AddJobListener<JobListener>();
// Add trigger listener
q.AddTriggerListener<TriggerListener>();
});7. Cluster Deployment
7.1 Configure Clustering
builder.Services.AddQuartz(q =>
{
q.UsePersistentStore(s =>
{
s.UseSqlServer(connectionString);
s.UseJsonSerializer();
s.UseClusteredStore(c =>
{
c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
c.CheckinInterval = TimeSpan.FromSeconds(10);
});
});
});7.2 Cluster Characteristics
| Feature | Description |
|---|---|
| Load balancing | Jobs are automatically distributed across cluster nodes |
| Failover | When a node fails, jobs are automatically transferred to other nodes |
| Data consistency | Database ensures consistent job state |
| Single execution | The same job runs only once in the cluster |
8. Practical Recommendations
8.1 Best Practices
- Job granularity: Each job should do one thing and keep a single responsibility
- Error handling: Catch exceptions in jobs to avoid task interruption
- Logging: Record job start, completion, and exception information
- Idempotency: Ensure jobs can be safely re-executed
- Resource cleanup: Release database connections, file handles, and other resources promptly
8.2 Performance Optimization
- Avoid long-running jobs: Split long-running jobs into multiple shorter jobs
- Reasonable scheduling: Avoid running many jobs at the same time
- Use async: Job execution should use asynchronous patterns
- Monitoring and alerts: Monitor job execution status and set up alerting
Summary
Quartz.NET is a powerful scheduled task framework that supports multiple trigger types and cluster deployment. Used properly, it can meet complex scheduling requirements.
Author: Lei Tao
Date: May 15, 2024