Quartz .NET是一个功能强大的作业调度库,用于在.NET应用程序中执行定时任务和后台处理工作。使用.NET C#开发Windows服务时,可以使用Quartz .NET定时执行任务。
使用.NET8开发的Windows服务中,可以使用Nuget安装Quartz .NET包。然后我们可以在Worker.cs的ExecuteAsync方法里创建调度程序。
首先要创建一个Job类,实现IJob接口,代码如下:
public class WebSiteResponseTimeJob : IJob
{
private readonly ILogger<WebSiteResponseTimeJob> _logger;
IDbContextFactory<ToolsContext> _contextFactory;
public WebSiteResponseTimeJob(ILogger<WebSiteResponseTimeJob> logger,IDbContextFactory<ToolsContext> contextFactory)
{
_logger = logger;
_contextFactory = contextFactory;
}
public async Task Execute(IJobExecutionContext context)
{
try
{
_logger.LogInformation("WebSiteResponseTimeJob running at: {time}", DateTimeOffset.Now);
// ...业务代码
}
catch (Exception ex)
{
_logger.LogError("WebSiteResponseTimeJob error at: {time}, message: {message}", DateTimeOffset.Now, ex.Message);
}
}
}
然后就可以创建和启动调度程序了:
var schedulerFactory = new StdSchedulerFactory();
var scheduler = await schedulerFactory.GetScheduler();
IJobDetail job = JobBuilder.Create<WebSiteResponseTimeJob>()
.WithIdentity("WebSiteResponseTimeJob")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("WebSiteResponseTimeJobTrigger")
.WithCronSchedule("0 0/10 * * * ?") //从0开始每隔10分钟执行一次
.Build();
await scheduler.ScheduleJob(job, trigger);
await scheduler.Start();
上面的代码中使用了Cron调度程序。希望每隔十分钟执行一次WebSiteResponseTimeJob任务。实际运行时发现任务没有被触发,而且也没有任何报错信息。
换了一个没有构造函数的Job类后发现可以正常触发,说明是因为Quartz.NET创建Job时不支持构造函数依赖注入。
网上查了一下解决方案是重新实现以下IJobFactory替换原来的JobFactory,在自己实现的JobFactory里创建Job时使用依赖注入注册的对象。
首先创建一个MyJobFactory.cs类,实现IJobFactory接口,代码如下:
public class MyJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public MyJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
在Program.cs类里注册要依赖注入的JobFactory和Job,比如我的MyJobFactory和WebSiteResponseTimeJob:
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(opt =>
{
opt.ServiceName = "MyTools.Service";
});
builder.Services.AddDbContextFactory<ToolsContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("Tools"));
});
// 注册JobFactory和Job
builder.Services.AddSingleton<IJobFactory, MyJobFactory>();
builder.Services.AddSingleton<WebSiteResponseTimeJob>();
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
host.Run();
然后在创建调度程序的时候替换掉Quartz原来的JobFactory就可以实现依赖注入了,把Worker.cs里的代码做如下修改:
var schedulerFactory = new StdSchedulerFactory();
var scheduler = await schedulerFactory.GetScheduler();
var myJobFactory = _serviceProvider.GetService<IJobFactory>();
if (myJobFactory != null)
{
scheduler.JobFactory = myJobFactory;
}
IJobDetail job = JobBuilder.Create<WebSiteResponseTimeJob>()
.WithIdentity("WebSiteResponseTimeJob")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("WebSiteResponseTimeJobTrigger")
.WithCronSchedule("0 0/10 * * * ?")
.Build();
await scheduler.ScheduleJob(job, trigger);
await scheduler.Start();
这样运行程序就可以正常创建Job对象和执行任务了。
下面是一些常用的Cron表达式:
"0 0 12 * * ?" 每天中午12点触发 "0 15 10 ? * *" 每天上午10:15触发 "0 15 10 * * ?" 每天上午10:15触发 "0 15 10 * * ? *" 每天上午10:15触发 "0 15 10 * * ? 2005" 2005年的每天上午10:15触发 "0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 "0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 "0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 "0 15 10 15 * ?" 每月15日上午10:15触发 "0 15 10 L * ?" 每月最后一日的上午10:15触发 "0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发 每天早上6点 0 6 * * * 每两个小时 0 */2 * * * 晚上11点到早上8点之间每两个小时,早上八点 0 23-7/2,8 * * * 每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点 0 11 4 * 1-3 1月1日早上4点 0 4 1 1 *
2