如何在ASP.NET Core中启动Quartz?

24

我有以下的类

 public class MyEmailService
 {
    public async Task<bool> SendAdminEmails()
    {
        ...
    }
    public async Task<bool> SendUserEmails()
    {
        ...
    }

 }
 public interface IMyEmailService
 {
    Task<bool> SendAdminEmails();
    Task<bool> SendUserEmails();
 }

我已经安装了最新的Quartz 2.4.1 Nuget包,因为我想在我的Web应用程序中使用一个轻量级调度程序,而不需要单独的SQL Server数据库。

我需要按照以下计划安排方法:

  • SendUserEmails 每周一、周二和周三的17:00运行
  • SendAdminEmails 每周四和周五的09:00运行

在ASP.NET Core中,我需要哪些代码来使用Quartz进行这些方法的调度?我还需要知道如何在ASP.NET Core中启动Quartz,因为互联网上的所有代码示例仍然参考早期版本的ASP.NET。

我可以找到一个代码示例,用于早期版本的ASP.NET,但我不知道如何在ASP.NET Core中启动Quartz以开始测试。 在ASP.NET Core中我应该把JobScheduler.Start();放在哪里?


6
你为什么要在ASP.NET应用程序中计划运行重复的后台任务?你知道这是一个非常错误的方法吗?请参考这里:http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/ 然后考虑将此逻辑移动到ASP.NET应用程序之外。例如,您可以使用定期由操作系统调度程序运行的控制台应用程序,或编写服务并使用Quartz在其中安排任务的计划。 - Darin Dimitrov
你到目前为止尝试了什么? - Tseng
1
我在互联网上找不到任何关于ASP.NET Core中JobScheduler.Start();等效的代码示例,该示例解释了http://www.mikesdotnetting.com/article/254/scheduled-tasks-in-asp-net-with-quartz-net。我应该能够解决调度问题,但在ASP.NET Core中启动Quartz仍然是一个谜。Google没有找到相关信息。 - dev2go
请参见 https://github.com/quartznet/quartznet/issues/355。 - Set
@Set 我也看到了这个,但是这是一年前的内容,这意味着它支持,但在互联网上没有关于Quartz在ASP.NET Core中如何工作的代码示例。 - dev2go
1
也许有点晚了,但我看到你使用的是NuGet包的2.4.1版本。它不支持.NET Core,我认为你应该看看3.0版本(https://www.quartz-scheduler.net/2016/08/16/quartznet-3.0-alpha1-released.html),现在已经有alpha版本可用(https://www.nuget.org/packages/Quartz/3.0.0-alpha2)。此外,迁移指南(https://www.quartz-scheduler.net/documentation/quartz-3.x/migration-guide.html)和3.x版本的教程(https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html)可以提供一些帮助。 - felix-b
5个回答

78

TL;DR(完整回答在下面)

假定使用的工具:Visual Studio 2017 RTM、.NET Core 1.1、.NET Core SDK 1.0、SQL Server Express 2016 LocalDB。

在Web应用程序.csproj中:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <!-- .... existing contents .... -->

  <!-- add the following ItemGroup element, it adds required packages -->
  <ItemGroup>
    <PackageReference Include="Quartz" Version="3.0.0-alpha2" />
    <PackageReference Include="Quartz.Serialization.Json" Version="3.0.0-alpha2" />
  </ItemGroup>

</Project>

在Visual Studio默认生成的Program类中:
public class Program
{
    private static IScheduler _scheduler; // add this field

    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .UseApplicationInsights()
            .Build();

        StartScheduler(); // add this line

        host.Run();
    }

    // add this method
    private static void StartScheduler()
    {
        var properties = new NameValueCollection {
            // json serialization is the one supported under .NET Core (binary isn't)
            ["quartz.serializer.type"] = "json",

            // the following setup of job store is just for example and it didn't change from v2
            // according to your usage scenario though, you definitely need 
            // the ADO.NET job store and not the RAMJobStore.
            ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
            ["quartz.jobStore.useProperties"] = "false",
            ["quartz.jobStore.dataSource"] = "default",
            ["quartz.jobStore.tablePrefix"] = "QRTZ_",
            ["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
            ["quartz.dataSource.default.provider"] = "SqlServer-41", // SqlServer-41 is the new provider for .NET Core
            ["quartz.dataSource.default.connectionString"] = @"Server=(localdb)\MSSQLLocalDB;Database=Quartz;Integrated Security=true"
        };

        var schedulerFactory = new StdSchedulerFactory(properties);
        _scheduler = schedulerFactory.GetScheduler().Result;
        _scheduler.Start().Wait();

        var userEmailsJob = JobBuilder.Create<SendUserEmailsJob>()
            .WithIdentity("SendUserEmails")
            .Build();
        var userEmailsTrigger = TriggerBuilder.Create()
            .WithIdentity("UserEmailsCron")
            .StartNow()
            .WithCronSchedule("0 0 17 ? * MON,TUE,WED")
            .Build();

        _scheduler.ScheduleJob(userEmailsJob, userEmailsTrigger).Wait();

        var adminEmailsJob = JobBuilder.Create<SendAdminEmailsJob>()
            .WithIdentity("SendAdminEmails")
            .Build();
        var adminEmailsTrigger = TriggerBuilder.Create()
            .WithIdentity("AdminEmailsCron")
            .StartNow()
            .WithCronSchedule("0 0 9 ? * THU,FRI")
            .Build();

        _scheduler.ScheduleJob(adminEmailsJob, adminEmailsTrigger).Wait();
    }
}

一个工作类的例子:

public class SendUserEmailsJob : IJob
{
    public Task Execute(IJobExecutionContext context)
    {
        // an instance of email service can be obtained in different ways, 
        // e.g. service locator, constructor injection (requires custom job factory)
        IMyEmailService emailService = new MyEmailService();

        // delegate the actual work to email service
        return emailService.SendUserEmails();
    }
}

完整答案

.NET Core的Quartz

首先,根据此公告,您必须使用v3的Quartz,因为它针对.NET Core。

目前,只有v3版本的alpha版可在NuGet上获得。看起来团队花了很多精力来发布2.5.0,但它并不针对.NET Core。尽管如此,在他们的GitHub存储库中,master分支已经专门用于v3,基本上,v3版本的未解决问题似乎并不重要,大多是旧的愿望清单项目,依我看。由于最近的提交活动相当低,我预计v3版本将在几个月内发布,或者可能半年 - 但没有人知道。

作业和IIS回收

如果Web应用程序将托管在IIS下,则必须考虑工作进程的回收/卸载行为。 ASP.NET Core Web应用程序作为常规的.NET Core进程运行,与w3wp.exe分开 - IIS仅充当反向代理。尽管如此,当w3wp.exe的实例被回收或卸载时,相关的.NET Core应用程序进程也会被通知退出(根据)。

Web应用程序也可以在非IIS反向代理(例如NGINX)后面进行自托管,但我将假定您使用IIS,并相应地缩小我的答案范围。

回收/卸载引入的问题在@darin-dimitrov提到的文章中很好地解释了:

  • 例如,在星期五9:00,由于几个小时前没有活动而被IIS卸载,进程已经停止,直到进程再次启动才会发送管理电子邮件。为了避免这种情况发生,请配置IIS以最小化卸载/回收(请参见此答案)。
    • 从我的经验来看,上述配置仍然不能保证IIS永远不会卸载应用程序。为了确保您的进程正常运行,您可以设置定期向您的应用程序发送请求的命令,从而保持其处于活动状态。
  • 当主机进程被回收/卸载时,必须优雅地停止作业,以避免数据损坏。

为什么要在Web应用程序中托管定时作业

我认为有一种正当理由将这些电子邮件作业托管在Web应用程序中,尽管存在上述问题。这是决策只有一种应用程序模型(ASP.NET)。这种方法简化了学习曲线、部署程序、生产监控等方面的工作。

如果您不想引入后端微服务(将电子邮件作业移动到其中的一个好地方),那么在Web应用程序中运行Quartz,克服IIS回收/卸载行为是有意义的。

或者你可能有其他理由。

持久作业存储

在您的情况下,作业执行的状态必须保存在进程外。因此,默认的RAMJobStore不适合,您必须使用ADO.NET作业存储

由于您在问题中提到了SQL Server,我将提供适用于SQL Server数据库的设置示例。

如何启动(和优雅地停止)调度程序

我假设您使用Visual Studio 2017和最新/最近版本的.NET Core工具。我的.NET Core Runtime 1.1和.NET Core SDK 1.0。

对于数据库设置示例,我将在SQL Server 2016 Express LocalDB中使用名为Quartz的数据库。可以在这里找到DB设置脚本。

首先,在Web应用程序.csproj中添加所需的包引用(或使用Visual Studio中的NuGet包管理器GUI完成):

<Project Sdk="Microsoft.NET.Sdk.Web">

  <!-- .... existing contents .... -->

  <!-- the following ItemGroup adds required packages -->
  <ItemGroup>
    <PackageReference Include="Quartz" Version="3.0.0-alpha2" />
    <PackageReference Include="Quartz.Serialization.Json" Version="3.0.0-alpha2" />
  </ItemGroup>

</Project>

通过迁移指南V3教程的帮助,我们可以了解如何启动和停止调度程序。 我们建议将此封装在一个单独的类中,命名为QuartzStartup

using System;
using System.Collections.Specialized;
using System.Threading.Tasks;
using Quartz;
using Quartz.Impl;

namespace WebApplication1
{
    // Responsible for starting and gracefully stopping the scheduler.
    public class QuartzStartup
    {
        private IScheduler _scheduler; // after Start, and until shutdown completes, references the scheduler object

        // starts the scheduler, defines the jobs and the triggers
        public void Start()
        {
            if (_scheduler != null)
            {
                throw new InvalidOperationException("Already started.");
            }

            var properties = new NameValueCollection {
                // json serialization is the one supported under .NET Core (binary isn't)
                ["quartz.serializer.type"] = "json",

                // the following setup of job store is just for example and it didn't change from v2
                ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
                ["quartz.jobStore.useProperties"] = "false",
                ["quartz.jobStore.dataSource"] = "default",
                ["quartz.jobStore.tablePrefix"] = "QRTZ_",
                ["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
                ["quartz.dataSource.default.provider"] = "SqlServer-41", // SqlServer-41 is the new provider for .NET Core
                ["quartz.dataSource.default.connectionString"] = @"Server=(localdb)\MSSQLLocalDB;Database=Quartz;Integrated Security=true"
            };

            var schedulerFactory = new StdSchedulerFactory(properties);
            _scheduler = schedulerFactory.GetScheduler().Result;
            _scheduler.Start().Wait();

            var userEmailsJob = JobBuilder.Create<SendUserEmailsJob>()
                .WithIdentity("SendUserEmails")
                .Build();
            var userEmailsTrigger = TriggerBuilder.Create()
                .WithIdentity("UserEmailsCron")
                .StartNow()
                .WithCronSchedule("0 0 17 ? * MON,TUE,WED")
                .Build();

            _scheduler.ScheduleJob(userEmailsJob, userEmailsTrigger).Wait();

            var adminEmailsJob = JobBuilder.Create<SendAdminEmailsJob>()
                .WithIdentity("SendAdminEmails")
                .Build();
            var adminEmailsTrigger = TriggerBuilder.Create()
                .WithIdentity("AdminEmailsCron")
                .StartNow()
                .WithCronSchedule("0 0 9 ? * THU,FRI")
                .Build();

            _scheduler.ScheduleJob(adminEmailsJob, adminEmailsTrigger).Wait();
        }

        // initiates shutdown of the scheduler, and waits until jobs exit gracefully (within allotted timeout)
        public void Stop()
        {
            if (_scheduler == null)
            {
                return;
            }

            // give running jobs 30 sec (for example) to stop gracefully
            if (_scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000)) 
            {
                _scheduler = null;
            }
            else
            {
                // jobs didn't exit in timely fashion - log a warning...
            }
        }
    }
}

注意1:在上面的例子中,SendUserEmailsJobSendAdminEmailsJob是实现IJob接口的类。 IJob接口与IMyEmailService略有不同,因为它返回void Task而不是Task<bool>。这两个作业类都应该将IMyEmailService作为依赖项(可能是构造函数注入)。
注意2:为了让长时间运行的作业能够及时退出,在IJob.Execute方法中,它应该观察IJobExecutionContext.CancellationToken的状态。这可能需要更改IMyEmailService接口,使其方法接收CancellationToken参数:
public interface IMyEmailService
{
    Task<bool> SendAdminEmails(CancellationToken cancellation);
    Task<bool> SendUserEmails(CancellationToken cancellation);
}

何时和在哪里启动和停止调度程序

在ASP.NET Core中,应用程序引导代码位于Program类中,就像在控制台应用程序中一样。调用Main方法创建Web主机,运行它,并等待其退出:

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .UseApplicationInsights()
            .Build();

        host.Run();
    }
}

最简单的方法就是在Main方法中直接调用QuartzStartup.Start,就像我在TL;DR中所做的那样。但由于我们还必须正确地处理进程关闭,因此我更喜欢以一种更一致的方式挂钩启动和关闭代码。
这行代码:
.UseStartup<Startup>()

指的是一个名为Startup的类,在使用Visual Studio创建新的ASP.NET Core Web应用程序项目时生成。 Startup类如下所示:

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        // scaffolded code...
    }

    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // scaffolded code...
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // scaffolded code...
    }
}

很明显,在 Startup 类的某个方法中应该插入对 QuartzStartup.Start 的调用。问题是,QuartzStartup.Stop 应该在哪里挂接。
在传统的 .NET Framework 中,ASP.NET 提供了 IRegisteredObject 接口。根据这篇文章文档,在 ASP.NET Core 中,它被替换为 IApplicationLifetime。很好。可以通过参数将 IApplicationLifetime 实例注入到 Startup.Configure 方法中。
为了保持一致性,我将同时将 QuartzStartup.StartQuartzStartup.Stop 挂接到 IApplicationLifetime 上:
public class Startup
{
    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(
        IApplicationBuilder app, 
        IHostingEnvironment env, 
        ILoggerFactory loggerFactory, 
        IApplicationLifetime lifetime) // added this parameter
    {
        // the following 3 lines hook QuartzStartup into web host lifecycle
        var quartz = new QuartzStartup();
        lifetime.ApplicationStarted.Register(quartz.Start);
        lifetime.ApplicationStopping.Register(quartz.Stop);

        // .... original scaffolded code here ....
    }

    // ....the rest of the scaffolded members ....
}

请注意,我已经扩展了Configure方法的签名,增加了一个额外的IApplicationLifetime参数。根据文档ApplicationStopping将阻塞,直到注册的回调完成。

在IIS Express和ASP.NET Core模块上进行优雅的关闭

我只能在安装了最新版本的ASP.NET Core模块的IIS上观察到 IApplicationLifetime.ApplicationStopping钩子的预期行为。与Visual Studio 2017 Community RTM一起安装的IIS Express(用于开发环境)和带有过时版本ASP.NET Core模块的IIS没有始终调用IApplicationLifetime.ApplicationStopping 。我认为这是因为已解决的此错误
您可以从这里安装最新版本的ASP.NET Core模块。请按照“安装最新ASP.NET Core模块”部分中的说明进行操作。

Quartz vs. FluentScheduler

我还看了一下FluentScheduler,因为@Brice Molesti提出了它作为替代库。我的第一印象是,与Quartz相比,FluentScheduler是一个相当简单和不成熟的解决方案。例如,FluentScheduler没有提供作业状态持久性和集群执行等基本功能。

1
同样适用于 .net core 2.0。 - James Blake
同样适用于 .net core 2.1。 - aleha_84
我知道在评论中用“干得好”并不好,但是说真的 - 干得好,这个回答包含了很多有价值且必要的信息,而且都集中在一个地方。 - ColinM

6

除了@felix-b的回答之外,还可以将DI添加到作业中。同时,可以将QuartzStartup Start设置为异步。

基于这个答案:https://dev59.com/klgQ5IYBdhLWcg3w1nVA#42158004

public class QuartzStartup 
{
    public QuartzStartup(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task Start()
    {
        // other code is same
        _scheduler = await schedulerFactory.GetScheduler();
        _scheduler.JobFactory = new JobFactory(_serviceProvider);

        await _scheduler.Start();
        var sampleJob = JobBuilder.Create<SampleJob>().Build();
        var sampleTrigger = TriggerBuilder.Create().StartNow().WithCronSchedule("0 0/1 * * * ?").Build();
        await _scheduler.ScheduleJob(sampleJob, sampleTrigger);
    }
}

JobFactory 类

public class JobFactory : IJobFactory
{
    private IServiceProvider _serviceProvider;
    public JobFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
    }

    public void ReturnJob(IJob job)
    {
        (job as IDisposable)?.Dispose();
    }
}

启动类:

public void ConfigureServices(IServiceCollection services)
{
     // other code is removed for brevity
     // need to register all JOBS by their class name
     services.AddTransient<SampleJob>();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
    var quartz = new QuartzStartup(_services.BuildServiceProvider());
    applicationLifetime.ApplicationStarted.Register(() => quartz.Start());
    applicationLifetime.ApplicationStopping.Register(quartz.Stop);

    // other code removed for brevity
}

构造函数依赖注入的SampleJob类:

public class SampleJob : IJob
{
    private readonly ILogger<SampleJob> _logger;

    public SampleJob(ILogger<SampleJob> logger)
    {
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        _logger.LogDebug("Execute called");
    }
}

3

我不知道如何使用Quartz来实现,但是我已经试验了另一个库来完成同样的场景,效果非常好。以下是我是如何做到的:

  • Install FluentScheduler

    Install-Package FluentScheduler
    
  • Use it like this

    var registry = new Registry();
    JobManager.Initialize(registry);
    
    JobManager.AddJob(() => MyEmailService.SendAdminEmails(), s => s
          .ToRunEvery(1)
          .Weeks()
          .On(DayOfWeek.Monday)
          .At(17, 00));
    JobManager.AddJob(() => MyEmailService.SendAdminEmails(), s => s
          .ToRunEvery(1)
          .Weeks()
          .On(DayOfWeek.Wednesday)
          .At(17, 00));
    
     JobManager.AddJob(() => MyEmailService.SendUserEmails(), s => s
           .ToRunEvery(1)
           .Weeks()
           .On(DayOfWeek.Thursday)
           .At(09, 00));
    
     JobManager.AddJob(() => MyEmailService.SendUserEmails(), s => s
           .ToRunEvery(1)
           .Weeks()
           .On(DayOfWeek.Friday)
           .At(09, 00));
    

文档可在GitHub的FluentScheduler页面找到。


1
三件事情。(1) GitHub 存储库中的文档在 ASP.NET Core 方面已经过时,(2) 上述示例应该连接到 Web 主机,但您没有展示如何连接,(3) 在我看来,缺少对 JobManager.StopAndBlock() 的调用。 - felix-b
嗨,回答你的问题:(1)在文档中有一个部分“与.NET Core一起使用”(2)我目前在.NET Core中使用我的示例,无需额外配置(我这里不使用DI,但您可以使用)(3)我动态地向此管理器添加作业,因此如果我停止并阻塞,则不会执行我的计划作业(仅当前正在运行的作业),也无法安排新的作业。 - Hayha
嗨@Brice,说得好。不过,你在ASP.NET Core应用程序背后使用IIS反向代理吗?一般来说,ASP.NET-Core-behind-IIS场景需要正确处理由AspNetCore IIS模块启动的进程卸载。这就是调用JobManager.StopAndBlock()的时候。然而,文档没有提及如何钩入Kestrel服务器生命周期。 - felix-b

1

我需要在ASP.NET Core中使用Quartz调度这些方法,需要哪些代码?我还需要知道如何在ASP.NET Core中启动Quartz,因为互联网上的所有代码示例仍然是针对以前版本的ASP.NET。

嗨,现在有一个很好的Quartz DI用于初始化和使用。

[DisallowConcurrentExecution]
public class Job1 : IJob
{
    private readonly ILogger<Job1> _logger;

    public Job1(ILogger<Job1> logger)
    {
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        _logger.LogInformation("Start job1");
        await Task.Delay(2, context.CancellationToken);
        _logger?.LogInformation("End job1");
    }
}
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        services.AddQuartz(cfg =>
        {
            cfg.UseMicrosoftDependencyInjectionJobFactory(opt =>
            {
                opt.AllowDefaultConstructor = false;
            });

            cfg.AddJob<Job1>(jobCfg =>
            {
                jobCfg.WithIdentity("job1");
            });

            cfg.AddTrigger(trigger =>
            {
                trigger
                    .ForJob("job1")
                    .WithIdentity("trigger1")
                    .WithSimpleSchedule(x => x
                        .WithIntervalInSeconds(10)
                        .RepeatForever());
            });
        });

        services.AddQuartzHostedService(opt =>
        {
            opt.WaitForJobsToComplete = true;
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // standart impl
    }
}

0

被接受的答案非常全面地涵盖了这个主题,但是随着最新的Quartz版本的更新,一些事情已经发生了变化。以下内容基于this article,展示了使用Quartz 3.0.x和ASP.NET Core 2.2的快速入门:

Util类

public class QuartzServicesUtilities
{
    public static void StartJob<TJob>(IScheduler scheduler, TimeSpan runInterval)
        where TJob : IJob
    {
        var jobName = typeof(TJob).FullName;

        var job = JobBuilder.Create<TJob>()
            .WithIdentity(jobName)
            .Build();

        var trigger = TriggerBuilder.Create()
            .WithIdentity($"{jobName}.trigger")
            .StartNow()
            .WithSimpleSchedule(scheduleBuilder =>
                scheduleBuilder
                    .WithInterval(runInterval)
                    .RepeatForever())
            .Build();

        scheduler.ScheduleJob(job, trigger);
    }
}

工作工厂

public class QuartzJobFactory : IJobFactory
{
    private readonly IServiceProvider _serviceProvider;

    public QuartzJobFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        var jobDetail = bundle.JobDetail;

        var job = (IJob)_serviceProvider.GetService(jobDetail.JobType);
        return job;
    }

    public void ReturnJob(IJob job) { }
}

一个与应用程序池回收/退出有关的工作示例。
[DisallowConcurrentExecution]
public class TestJob : IJob
{
    private ILoggingService Logger { get; }
    private IApplicationLifetime ApplicationLifetime { get; }

    private static object lockHandle = new object();
    private static bool shouldExit = false;

    public TestJob(ILoggingService loggingService, IApplicationLifetime applicationLifetime)
    {
        Logger = loggingService;
        ApplicationLifetime = applicationLifetime;
    }

    public Task Execute(IJobExecutionContext context)
    {
        return Task.Run(() =>
        {
            ApplicationLifetime.ApplicationStopping.Register(() =>
            {
                lock (lockHandle)
                {
                    shouldExit = true;
                }
            });

            try
            {
                for (int i = 0; i < 10; i ++)
                {
                    lock (lockHandle)
                    {
                        if (shouldExit)
                        {
                            Logger.LogDebug($"TestJob detected that application is shutting down - exiting");
                            break;
                        }
                    }

                    Logger.LogDebug($"TestJob ran step {i+1}");
                    Thread.Sleep(3000);
                }
            }
            catch (Exception exc)
            {
                Logger.LogError(exc, "An error occurred during execution of scheduled job");
            }
        });
    }
}

Startup.cs 配置

private void ConfigureQuartz(IServiceCollection services, params Type[] jobs)
{
    services.AddSingleton<IJobFactory, QuartzJobFactory>();
    services.Add(jobs.Select(jobType => new ServiceDescriptor(jobType, jobType, ServiceLifetime.Singleton)));

    services.AddSingleton(provider =>
    {
        var schedulerFactory = new StdSchedulerFactory();
        var scheduler = schedulerFactory.GetScheduler().Result;
        scheduler.JobFactory = provider.GetService<IJobFactory>();
        scheduler.Start();
        return scheduler;
    });
}

protected void ConfigureJobsIoc(IServiceCollection services)
{
    ConfigureQuartz(services, typeof(TestJob), /* other jobs come here */);
}

public void ConfigureServices(IServiceCollection services)
{
    ConfigureJobsIoc(services);

    // other stuff comes here
    AddDbContext(services);
    AddCors(services);

    services
        .AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}


protected void StartJobs(IApplicationBuilder app, IApplicationLifetime lifetime)
{
    var scheduler = app.ApplicationServices.GetService<IScheduler>();
    //TODO: use some config
    QuartzServicesUtilities.StartJob<TestJob>(scheduler, TimeSpan.FromSeconds(60));

    lifetime.ApplicationStarted.Register(() => scheduler.Start());
    lifetime.ApplicationStopping.Register(() => scheduler.Shutdown());
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory,
    ILoggingService logger, IApplicationLifetime lifetime)
{
    StartJobs(app, lifetime);

    // other stuff here
}

1
我猜QuartzServicesUtilities是在这里找到的:https://stackoverflow.com/questions/55211511/quartz-net-3-0-seems-to-launch-all-jobs-in-the-same-scope - Sandor Drieënhuizen
是的,就是这样。谢谢。 - Alexei - check Codidact

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接