refs:
Creating a Quartz.NET hosted service with ASP.NET Core
Using scoped services inside a Quartz.NET hosted service with ASP.NET Core
本文基于上面2个链接的文章翻译攥写。
在Quartz.Net中无法使用scoped service比如DbContext,一般IJob只能是单例模式或Transient模式;
1)为了使用scoped servcie 一般是注入IServiceProvice,然后创建scope环境获取实例。
比如:
public class EmailReminderJob : IJob
{
private readonly IServiceProvider _provider;
public EmailReminderJob( IServiceProvider provider)
{
_provider = provider;
}
public Task Execute(IJobExecutionContext context)
{
using(var scope = _provider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<AppDbContext>();
var emailSender = scope.ServiceProvider.GetService<IEmailSender>();
// fetch customers, send email, update DB
}
return Task.CompletedTask;
}
}
在很多场景下这个是可行的,此时IJob依然在DI中是单例模式。
2)创建一个辅助任务
不直接在IJob中实现,而是通过一个QuartzJobRunner的任务间接实现,在次任务中创建新的任务来实现;
QuartzJobRunner
.依然是单例模式,这样就不用担心会被 disposed掉.
services.AddSingleton<QuartzJobRunner>();
在QuartzJobRunner中创建一个
实例化的新IJob
并执行:
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using System;
using System.Threading.Tasks;
public class QuartzJobRunner : IJob
{
private readonly IServiceProvider _serviceProvider;
public QuartzJobRunner(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task Execute(IJobExecutionContext context)
{
using (var scope = _serviceProvider.CreateScope())
{
var jobType = context.JobDetail.JobType;
var job = scope.ServiceProvider.GetRequiredService(jobType) as IJob;
await job.Execute(context);
}
}
}
这么做有2个优点:
- 我们注册
EmailReminderJob
为scoped service,并直接注入依赖到其构造方法中; - 我们可以将其他跨领域问题转移到QuartzJobRunner类中.
由于job的实例源自 scoped IServiceProvder
,所以可以在构造方法中使用 scoped services。
如果你不熟悉DI范围问题,那么理解这些问题可能会很棘手。
[DisallowConcurrentExecution]
public class EmailReminderJob : IJob
{
private readonly AppDbContext _dbContext;
private readonly IEmailSender _emailSender;
public EmailReminderJob(AppDbContext dbContext, IEmailSender emailSender)
{
_dbContext = dbContext;
_emailSender = emailSender;
}
public Task Execute(IJobExecutionContext context)
{
// fetch customers, send email, update DB
return Task.CompletedTask;
}
}
这些 IJob
实现可以注册为任何生命周期(scoped or transient),当然 JobSchedule
依然是单例:
services.AddScoped<EmailReminderJob>();
services.AddSingleton(new JobSchedule(
jobType: typeof(EmailReminderJob),
cronExpression: "0 0 12 * * ?")); // every day at noon
QuartzJobRunner
处理跨界问题。
This example is obviously very basic. If the code here looks ok to you, I suggest watching Jimmy Bogard's "Six Little Lines of Fail" talk, which describes some of the issues!
public class QuartzJobRunner : IJob
{
private readonly IServiceProvider _serviceProvider;
public QuartzJobRunner(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task Execute(IJobExecutionContext context)
{
using (var scope = _serviceProvider.CreateScope())
{
var jobType = context.JobDetail.JobType;
var job = scope.ServiceProvider.GetRequiredService(jobType) as IJob;
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var messageBus = scope.ServiceProvider.GetRequiredService<IBus>();
await job.Execute(context);
// job completed, save dbContext changes
await dbContext.SaveChangesAsync();
// db transaction succeeded, send messages
await messageBus.DispatchAsync();
}
}
}
QuartzJobRunner
以上实现类似前面的,但是我们创建job后,从DI容器中获取了 DbContext和messageBus,等执行完job后再保存到db并且发送消息到事件总线。
.
把这些方法放在 QuartzJobRunner
中可以减少在具体 IJob中的实现,并且更容易实现其他模式。
这里展示的方法(使用中间的QuartzJobRunner类),主要有两个原因:
- 您的其他IJob实现不需要任何关于创建作用域的基础架构的知识,只需要避免标准构造函数注入
- IJobFactory不必做任何特殊的事情来处理处理处理工作。QuartzJobRunner通过创建和处理作用域来隐式地处理这个问题。
其他实现方法见
Matthew Abbot demonstrates an approach in this gist
由于您必须匹配的接口API,它有点笨重,但可以说它更接近于您应该实现它的方式!就我个人而言,我认为我会坚持QuartzJobRunner的方法,但选择最适合你的方法。