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

相关推荐
燃先生._.36 分钟前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
Dream_Snowar38 分钟前
速通Python 第三节
开发语言·python
高山我梦口香糖2 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235242 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
信号处理学渣2 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客2 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
jasmine s2 小时前
Pandas
开发语言·python
biomooc2 小时前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言
骇客野人2 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言