在Quartz.Net中使用Scoped Service

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的方法,但选择最适合你的方法。

相关推荐
陈随易7 小时前
专注飞机 FocusFlight,两个人把番茄钟做成了一趟航班
前端·后端·程序员
总要学点什么7 小时前
第98篇:三维特效-材质线效果——给一条线换十种材质,虚线的图案只填了一半
开发语言·前端·javascript·ecmascript·gis·webgl·材质
10share7 小时前
基于大模型的 i18n 方案:把中文原文当 Key,轻松搞定多语言维护
前端·vue.js
田甲7 小时前
做一款 Type-C 母座读卡器:GL3224 双卡方案实测
c语言·开发语言
严肃小白兔8 小时前
MATLAB 绘图与科研论文插图制作:图形设计、统计表达与矢量导出
开发语言·matlab·科研绘图
OpsEye8 小时前
混合云 AI 架构,如何统一做敏感内容拦截?
javascript·ai编程
Funlang10 小时前
给机器写的 JavaScript 造个笼子
开发语言·javascript·ecmascript
普通人610 小时前
PHP文件读写完全指南
开发语言·php·1024程序员节
古茗前端团队10 小时前
由 key 引发的思考
前端·javascript·react.js
kyriewen10 小时前
我扫了 422 个仓库,近一半 CI 在裸装依赖
前端·javascript·程序员