quartz job单例_【半译】在ASP.NET Core中创建内部使用作用域服务的Quartz.NET宿主服务...

85201340c9aa24d3c261ee3e0efabffb.gif

在我的上一篇文章《在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度》,我展示了如何使用ASP.NET Core创建Quartz.NET托管服务并使用它来按计划运行后台任务。不幸的是,由于Quartz.NET API的工作方式,在Quartz作业中使用Scoped依赖项注入服务有些麻烦。说明下这篇文章部分采用机翻。

作者:依乐祝

译文地址:https://www.cnblogs.com/yilezhu/p/12757411.html

在这篇文章中,我将展示一种简化工作中使用Scoped服务的方法。您可以使用相同的方法来管理EF Core的工作单元模式和其他面向切面的模型。

这篇文章是上篇文章引申出来的,因此,如果您还没有阅读的话,建议您先阅读上篇文章。

回顾-自定义JobFactory和单例的IJob

在上篇博客的最后,我们有一个实现了IJob接口并向控制台简单输出信息的HelloWorldJob

public class HelloWorldJob : IJob{    private readonly ILogger<HelloWorldJob> _logger;    public HelloWorldJob(ILogger<HelloWorldJob> logger)    {        _logger = logger;    }    public Task Execute(IJobExecutionContext context)    {        _logger.LogInformation("Hello world!");        return Task.CompletedTask;    }}

我们还有一个IJobFactory的实现,以便我们在需要时从DI容器中检索作业的实例:

public class SingletonJobFactory : IJobFactory{    private readonly IServiceProvider _serviceProvider;    public SingletonJobFactory(IServiceProvider serviceProvider)    {        _serviceProvider = serviceProvider;    }    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)    {        return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;    }    public void ReturnJob(IJob job) { }}

这些服务都在Startup.ConfigureServices()中以单例形式注册:

services.AddSingleton();services.AddSingleton();

对于这个非常基本的示例来说,这很好,但是如果您需要在IJob内部使用一些范围服务呢?例如,也许您需要使用EF Core DbContext遍历所有客户,并向他们发送电子邮件,并更新客户记录。我们假设这个任务为EmailReminderJob

权宜之计

我在上一篇文章中展示的解决方案是将IServiceProvider注入到您的IJob的文档中,手动创建一个范围,并从中检索必要的服务。例如:

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;    }}

在许多情况下,这种方法绝对可以。如果不是将实现直接放在工作内部(如我上面所做的那样),而是使用中介者模式来处理诸如工作单元或消息分发之类的跨领域问题,则尤其如此。

如果不是这种情况,您可能会受益于创建一个可以为您管理这些工作的帮助类。

QuartzJobRunner

要解决这些问题,您可以创建一个IJob的“中间” 实现,这里我们命名为QuartzJobRunner,该实现位于IJobFactory和要运行的IJob之间。我将很快介绍作业实现,但是首先让我们更新现有的IJobFactory实现以无论请求哪个作业,始终返回QuartzJobRunner的实例,:

using Microsoft.Extensions.DependencyInjection;using Quartz;using Quartz.Spi;using System;public class JobFactory : IJobFactory{    private readonly IServiceProvider _serviceProvider;    public JobFactory(IServiceProvider serviceProvider)    {        _serviceProvider = serviceProvider;    }    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)    {        return _serviceProvider.GetRequiredService<QuartzJobRunner>();    }    public void ReturnJob(IJob job) { }}

如您所见,该NewJob()方法始终返回QuartzJobRunner的实例。我们将在Startup.ConfigureServices()中将QuartzJobRunner注册为单例模式,因此我们不必担心它没有被明确释放。

services.AddSingleton<QuartzJobRunner>();

我们将在QuartzJobRunner中创建实际所需的IJob实例。QuartzJobRunner中的job会创建范围,实例化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);        }    }}

在这一点上,您可能想知道,通过添加这个额外的间接层,我们获得了什么好处?主要有以下两个主要优点:

  • 我们可以将EmailReminderJob注册为范围服务,并直接将任何依赖项注入其构造函数中

  • 我们可以将其他横切关注点转移到QuartzJobRunner类中。

作业可以直接使用作用域服务

由于作业实例是从IServiceProvder作用域中解析来的,因此您可以在作业实现的构造函数中安全地使用作用域服务。这使的EmailReminderJob的实现更加清晰,并遵循构造函数注入的典型模式。如果您不熟悉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的实现可以使用以下任何生存期(作用域或瞬态)来在Startup.ConfigureServices()中注册(JobSchedule仍然可以是单例):

services.AddScoped<EmailReminderJob>();services.AddSingleton(new JobSchedule(    jobType: typeof(EmailReminderJob),    cronExpression: "0 0 12 * * ?")); // every day at noon

QuartzJobRunner可以处理横切关注点

QuartzJobRunner处理正在执行的IJob的整个生命周期:它从容器中获取,执行并释放它(在释放范围时)。因此,它很适合处理其他跨领域问题。

例如,假设您有一个需要更新数据库并将事件发送到消息总线的服务。您可以在每个单独的IJob实现中处理所有这些问题,也可以将跨领域的“提交更改”和“调度消息”操作移到QuartzJobRunner中。

这个例子显然是非常基础的。如果这里的代码适合您,我建议您观看吉米·博加德(Jimmy Bogard)的“六小段失败线”演讲,其中描述了一些问题!

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 = _serviceProvider.GetRequiredService<AppDbContext>();            var messageBus = _serviceProvider.GetRequiredService<IBus>();            await job.Execute(context);            // job completed, save dbContext changes            await dbContext.SaveChangesAsync();            // db transaction succeeded, send messages            await messageBus.DispatchAsync();        }    }}

这里的QuartzJobRunner实现与上一个非常相似,但是在执行的我们请求的IJob之前,我们从DI容器中解析了DbContext和消息总线服务。当作业成功执行后(即未抛出异常),我们将所有未提交的更改保存在中DbContext,并在消息总线上调度事件。

将这些方法移到QuartzJobRunner中应该可以减少IJob实现中的重复代码,并且可以更容易地移到更正式的管道和其他模式(如果您希望以后这样做的话)。

可替代解决方案

我喜欢本文中显示的方法(使用中间QuartzJobRunner类),主要有两个原因:

  • 您的其他IJob实现不需要任何有关创建作用域的基础结构的知识,只需完成标准构造函数注入即可

  • IJobFactory中不需要做做任何特殊处理工作。该QuartzJobRunner通过创建和处理作用域隐式地处理这个问题。

但是,此处显示的方法并不是在工作中使用范围服务的唯一方法。马修·阿伯特(Matthew Abbot) 在这个文章中演示了一种方法,该方法旨在以正确处理运行后的作业的方式实现IJobFactory。它有点笨拙,因为你必须匹配接口API,但可以说它更接近你应该实现它的方式!我个人认为我会坚持使用这种QuartzJobRunner方法,但是你可以选择最适合您的方法?

总结

在本文中,我展示了如何创建中间层IJob,该中间层QuartzJobRunner在调度程序需要执行作业时创建。该运行程序负责创建一个DI范围,实例化请求的作业并执行它,因此最终IJob实现可以在其构造函数中使用作用域中的服务。您也可以使用此方法在QuartzJobRunner中配置基本管道,尽管对此有更好的解决方案,例如装饰器或MediatR库中的行为。

往期 精彩 回顾

【推荐】.NET Core开发实战视频课程 ★★★

.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

【.NET Core微服务实战-统一身份认证】开篇及目录索引

Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)

.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

10个小技巧助您写出高性能的ASP.NET Core代码

用abp vNext快速开发Quartz.NET定时任务管理界面

在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度

现身说法:实际业务出发分析百亿数据量下的多表查询优化

关于C#异步编程你应该了解的几点建议

C#异步编程看这篇就够了

给我好看

284479ec0c9408fcb902bbbeeaaea531.png

您看此文用

 493126ceb1d37e0f8278c679a7e6c3f1.gif 1d1eb8a3b4da978d5c547e8f6966a18d.gif·03bab5682cc3943d980354961a300b7f.gif ab439fc652f63b3824bb3416a0dc15cc.gif

秒,转发只需1秒呦~

74d3369131823c0fa1445de22095b739.png

好看你就

点点

cf083e481a7b3518d372891e9feb9a83.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值