Standard use of Quartz task scheduling in. Net Core 2.2

Quartz.Net is a very popular task scheduling component, which must be used in many systems. Today, I'll introduce its standard use on Microsoft's latest. Net Core 2.2, and roughly describe the incorrect use methods on the Internet. Of course, those are right.

There are three ways to do. Net core+Quartz task scheduling on the Internet. Let me analyze it and tell you where the disadvantages are

The first is to host the Jobs service with Microsoft.AspNetCore.WebHost
Before. Net Core 2.1 was released, it is understandable to use WebHost to host Jobs service. After all, there is no official hosting method for. Net Core console applications. Since. Net Core 2.1, Microsoft has added Microsoft.Extensions.Hosting. In fact, the source code is separated from Microsoft.AspNetCore.WebHost and simplified, Remove Web related dependencies.

The second: write the task and trigger in the code without using the configuration file
For example, such encapsulation: quartzhelpers. Startasync < HelloWorld job > ("0 03 * *?" "," myjob "," mytrigger "," mygroup ");

The third is to realize the mapping of configuration relationship by yourself, which is similar to the following implementation. This implementation is unnecessary for most users, because the configuration file can do it

public class QuartzOption
{
    public QuartzOption(IConfiguration config)
    {
        if (config == null)
        {
            throw new ArgumentNullException(nameof(config));
        }

        var section = config.GetSection("quartz");
        section.Bind(this);
    }

    public Scheduler Scheduler { get; set; }

    public ThreadPool ThreadPool { get; set; }

    public Plugin Plugin { get; set; }

    public NameValueCollection ToProperties()
    {
        var properties = new NameValueCollection
        {
            ["quartz.scheduler.instanceName"] = Scheduler?.InstanceName,
            ["quartz.threadPool.type"] = ThreadPool?.Type,
            ["quartz.threadPool.threadPriority"] = ThreadPool?.ThreadPriority,
            ["quartz.threadPool.threadCount"] = ThreadPool?.ThreadCount.ToString(),
            ["quartz.plugin.jobInitializer.type"] = Plugin?.JobInitializer?.Type,
            ["quartz.plugin.jobInitializer.fileNames"] = Plugin?.JobInitializer?.FileNames
        };

        return properties;
    }
}

I mentioned three non-standard postures above. Now let me talk about standard postures. The whole demo project contains 6 files, including 2 configuration files

1. Let's talk about Job class first

namespace QuartzNetCore
{
    [DisallowConcurrentExecution]
    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;
        }
    }
}

I have to talk about the disallowcurrentexecution property. This property tells this HelloWorld Job that each execution is executed one by one. The previous execution is not completed, and the subsequent execution is not allowed. That is, the concurrent execution of HelloWorld Job is prohibited, and the execution of other jobs is not affected.

2. Talk about host classes

namespace QuartzNetCore
{
    public class HostServer : IHostedService
    {
        private readonly ILogger _logger;
        private readonly ISchedulerFactory _schedulerFactory;
        private readonly IJobFactory _jobFactory;

        public HostServer(ILogger<HostServer> logger, ISchedulerFactory schedulerFactory, IJobFactory jobFactory)
        {
            _logger = logger;
            _schedulerFactory = schedulerFactory;
            _jobFactory = jobFactory;
        }

        public IScheduler Scheduler { get; set; }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("start Quartz dispatch...");

            Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            Scheduler.JobFactory = _jobFactory;
            await Scheduler.Start(cancellationToken);
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("stop it Quartz dispatch...");

            await Scheduler.Shutdown(cancellationToken);
        }
    }
}

IHostedService interface comes from Microsoft.Extensions.Hosting. It is a lightweight host interface. It provides start and stop methods, which is very friendly to docker deployment. At the same time, in the startup method, you can see that the JobFactory is reassigned so that the Job class can also support dependency injection. The above Job class public helloworldjob (ilogger < helloworldjob > logger) is an interface injection.

3. Look at the task factory

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

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

        public void ReturnJob(IJob job) { }
    }
}

The function of this class is to implement dependency injection of Job, as mentioned earlier.

4. Task and trigger configuration

  <schedule>
    <job>
      <name>HelloWorldJob</name>
      <group>TestGroup</group>
      <description>HelloWorldJob Test task</description>
      <job-type>QuartzNetCore.HelloWorldJob, QuartzNetCore</job-type>
      <durable>true</durable>
      <recover>false</recover>
    </job>
    <trigger>
      <simple>
        <name>TestTrigger</name>
        <group>TestGroup</group>
        <description>TestTrigger Test trigger</description>
        <job-name>HelloWorldJob</job-name>
        <job-group>TestGroup</job-group>
        <repeat-count>-1</repeat-count>
        <repeat-interval>1000</repeat-interval>
      </simple>
    </trigger>
  </schedule>
</job-scheduling-data>

Note that the job type must be: class name and assembly name. Repeat count is - 1, that is, the execution is repeated for an unlimited number of times. Repeat interval is the execution interval, and 1000 is 1 second.

5. Configure plug-in configuration, which mainly specifies the job configuration file, plug-in class and assembly, and the number of threads

quartz.scheduler.instanceName = QuartzTest

quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
quartz.threadPool.threadCount = 10
quartz.threadPool.threadPriority = Normal

quartz.plugin.jobInitializer.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins
quartz.plugin.jobInitializer.fileNames = ~/quartz_jobs.xml

As I said, this file will be found automatically. As long as you put it in the following directory, the Quartz.Plugins plug-in will interpret it, so you can't see any reference to it in the whole project.

6. Startup class

class Program
{
    static void Main(string[] args)
    {
        var host = new HostBuilder()
            .ConfigureHostConfiguration(configHost =>
            {
                configHost.SetBasePath(Directory.GetCurrentDirectory());
                //configHost.AddJsonFile("hostsettings.json", true, true);
                //configHost.AddEnvironmentVariables("ASPNETCORE_");
                //configHost.AddCommandLine(args);
            })
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                configApp.AddJsonFile("appsettings.json", true);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true);
                configApp.AddEnvironmentVariables();
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddLogging();
                services.AddOptions();
                services.AddSingleton<IJobFactory, JobFactory>();
                services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
                services.AddHostedService<HostServer>();
                services.AddSingleton<HelloWorldJob>();
            })
            .ConfigureLogging((hostContext, configLogging) =>
            {
                configLogging.AddConsole();
            })
            .UseConsoleLifetime()
            .Build();

        host.Run();
    }
}

Set the directory and add the configuration file. The important thing is to add the host class HostServer, as well as the registered interface and implementation to ensure the normal operation of dependency injection.

Summary: using Quartz.Net in this way is the lightest and has the smallest code. Job s that need to be added later are implemented in the configuration file without modifying c# code.

Tags: Docker

Posted on Sat, 11 Sep 2021 21:48:50 -0400 by phrozenflame