Quartz 任务调度在 .Net Core 2.2 中的标准使用

本文介绍了在.NET Core 2.2中使用Quartz.Net进行任务调度的标准方法,批评了网上常见的三种不推荐做法,并提供了详细的代码示例。标准方法包括使用Microsoft.Extensions.Hosting宿主服务,通过配置文件管理任务与触发器,以及利用依赖注入。文中还阐述了如何避免并发执行问题,以及配置插件和线程池。
摘要由CSDN通过智能技术生成

Quartz.Net 是一款非常流行的任务调度组建,在很多系统中都必然要使用到,今天我就微软最新发布的.Net Core 2.2来介绍一下它的标准使用,也大致说下网上的那些不太正确的使用方式,当然那些也没错。

网上有三种方式来做 .Net core+Quartz 的任务调度,我来分析它,说说的弊端在哪里

第一种:用 Microsoft.AspNetCore.WebHost 来宿主Jobs服务
在.Net Core 2.1 还没有发布之前,使用 WebHost 宿主Jobs服务那也无可厚非,毕竟 .Net Core 控制台应用程序还没有官方的宿主方式,自打 .Net Core 2.1 开始,微软新增了 Microsoft.Extensions.Hosting ,其实看源码就是从 Microsoft.AspNetCore.WebHost 中分离出来,做了简化,把Web相关依赖去掉了。

第二种:把任务与触发器都写在了代码中,而没有使用配置文件
比如这样的封装:QuartzHelpers.StartAsync<HelloWorldJob>("0 0 3 * * ?", "myjob", "mytrigger", "mygroup");

第三种:自己实现配置关系的映射,类似如下实现,这个自己实现对大部分用户都是不必要的,因为配置文件就可以做到

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

上面说了3个我认为不标准的姿势,现在我来说说标准的姿势,整个demo项目包含6个文件,包括2个配置文件在内

1. 先说说Job

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

我得说说DisallowConcurrentExecution属性,这个属性告诉这个HelloWorldJob,每次执行都是一个挨一个执行,前面那次没有执行完成,后面那次执行是不允许进去的,也就是禁止HelloWorldJob的并发执行,对其他Job的执行不受影响。

2. 聊聊宿主类

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("开始Quartz调度...");

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

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("停止Quartz调度...");

            await Scheduler.Shutdown(cancellationToken);
        }
    }
}

IHostedService接口来之Microsoft.Extensions.Hosting,是一个轻量级的宿主接口,它提供启动与停止方法,这个对于docker部署非常友好。同时在这个启动方法里面,可以看见重新赋值了JobFactory,这个是为了可以让Job类也支持依赖注入。上面的Jobpublic HelloWorldJob(ILogger<HelloWorldJob> logger)就是一个接口注入。

3. 看看任务工厂

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) { }
    }
}

这个类的作用就是实现Job的依赖注入,前面已经提到。

4. 任务与触发器配置

  <schedule>
    <job>
      <name>HelloWorldJob</name>
      <group>TestGroup</group>
      <description>HelloWorldJob测试任务</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测试触发器</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>

其中job-type得注意,这个必须是:类名,程序集名。repeat-count-1即无限的次数重复执行,repeart-internval是执行间隔,1000即为1秒。

5. 配置插件配置,主要指定job的配置文件,插件的类与程序集,还有线程数等

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

我的说说,这个文件是会被自动找到的,只要你将它放到跟目录,Quartz.Plugins插件会解读它,所以整个项目中你看不见哪个地方有引用它。

6. 启动类

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

设置跟目录,添加配置文件,重要的是添加宿主类HostServer,还有注册接口与实现,确保依赖注入能正常运行。

总结:这个方式使用Quartz.Net是最轻量级的,也是具有最小代码的,后面需要添加Job都是在配置文件中来实现,无需修改c#代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值