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

相关推荐
洋不写bug5 小时前
二叉树(二) 常见基础操作解析|结点数、树高、查找结点、判断完全二叉树
java·开发语言·数据结构·完全二叉树·二叉树结点数·树高·查找结点
果果燕5 小时前
实习笔记(六)主机按钮状态上报完整流程
开发语言·c++·php
平头哥技术团队6 小时前
Day 16 | 用 ul、ol、li 一个标签,把散资料收成有序的技能清单页
开发语言·前端·html·html5
代码青铜z6 小时前
不会写代码做小程序,模板开发和AI 编程靠谱吗?创业前先看这 5 件事
前端
统计学小王子6 小时前
数学建模国赛倒计时 2 天 ——《统计模型精讲(广义线性模型 R 语言实例 + 分析)》
开发语言·数学建模·r语言
小傅哥6 小时前
Java + DDD,1:1 复刻 Deepseek Harness 项目
前端·后端·ai编程
此冬歌咏6 小时前
自动化服务器运维监控系统(python+shell)
linux·运维·服务器·开发语言·python·自动化
积硅步致千里6 小时前
Word 里的 .wmf 其实是 EMF:一次 metafile 转 PNG 排障
前端·后端
mayaairi6 小时前
Vue2 组件通讯(二):ref、自定义事件与provide/inject实战
前端·javascript·vue.js
Cx330❀6 小时前
【Linux网络】深入TCP协议:从滑动窗口到拥塞控制与性能优化全景解析
linux·开发语言·网络·tcp/ip·ai·性能优化·ai编程