在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个优点:

  • 我们注册EmailReminderJobscoped 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的方法,但选择最适合你的方法。

相关推荐
心平气和量大福大2 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai3 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜3 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa3 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio
hoLzwEge3 小时前
拯救低效调试!code-inspector-plugin 让代码定位如虎添翼
前端·前端框架·node.js
J船长3 小时前
烂笔头扫盲:LLM Eval 入门:从「感觉挺好」到「数据说话」的工程化落地
前端
skiyee3 小时前
被多个 UView 系 UI 库 “抄” 的 @uni-ku/root 究竟怎么实现?
前端
鱼毓屿御3 小时前
从「只会聊」到「边想边做」的跃迁
前端·学习·react.js
sugar__salt4 小时前
Vue3 表单双向绑定与响应式核心 API 技术详解
前端·javascript·vue.js
噢,我明白了4 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel