• Hosted Services + Quartz实现定时任务调度


    Hosted Services + Quartz实现定时任务调度

    https://www.cnblogs.com/Erik_Xu/p/9219307.html

    背景
      之前一直有朋友问,.Net Core + Linux环境有没有类似Windows服务的东西。其实是有的,我了解的方法有两种:

      #1 创建一个ASP.Net Core的Web项目(如Web API),然后通过添加中间件(Middleware)的方式来启动任务;

      #2 创建一个.Net Core的项目,添加Host,Dependency Injection,Configuration等组件,然后通过Main方法或中间件的方式启动服务。

      但是,上述两种方法都有点不足,如:

      #1 会把Web的生命周期引进来,但实际上,我们并不需要Web的功能,如Controller;

      #2 本身是没有问题的,但是对开发者的要求相对高一点点,需要对.Net Core的各个组成部分都有一定的认识,简而言之,门槛有一丢丢高。

      .Net Core 2.1推出了一个Generic Host的概念,可以很好的解决上面两种方法的不足:

      

      至于为什么选择Quartz来做调度,我想可能是因为情怀吧,因为之前是用的TopShelf+Quartz,其实Hangfire也不错。

    使用Hosted Service

    1. 创建一个控制台程序。

    2. 添加Host Nuget包。

    复制代码
    Install-Package Microsoft.Extensions.Hosting -Version 2.1.0
    Install-Package Microsoft.Extensions.Configuration.FileExtensions -Version 2.1.0
    Install-Package Microsoft.Extensions.Configuration.Json -Version 2.1.0
    Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables -Version 2.1.0
    Install-Package Microsoft.Extensions.Configuration.CommandLine -Version 2.1.0
    Install-Package Microsoft.Extensions.Logging.Console -Version 2.1.0
    Install-Package Microsoft.Extensions.Logging.Debug -Version 2.1.0
    复制代码

    1. 添加一个基于Timer的简单Hosted Service(用于简单演示),继承IHostedService。

    复制代码
    internal class TimedHostedService : IHostedService, IDisposable
    {
    private readonly ILogger _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }
    
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");
    
        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
    
        return Task.CompletedTask;
    }
    
    private void DoWork(object state)
    {
        _logger.LogInformation(string.Format("[{0:yyyy-MM-dd hh:mm:ss}]Timed Background Service is working.", DateTime.Now));
    }
    
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");
    
        _timer?.Change(Timeout.Infinite, 0);
    
        return Task.CompletedTask;
    }
    
    public void Dispose()
    {
        _timer?.Dispose();
    }
    

    }
    复制代码

    1. Main函数中添加Host的相关代码。

    复制代码
    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();
    //configApp.AddCommandLine(args);
    })
    .ConfigureServices((hostContext, services) =>
    {
    services.AddLogging();
    services.AddHostedService();
    })
    .ConfigureLogging((hostContext, configLogging) =>
    {
    configLogging.AddConsole();
    if (hostContext.HostingEnvironment.EnvironmentName == EnvironmentName.Development)
    {
    configLogging.AddDebug();
    }
    })
    .UseConsoleLifetime()
    .Build();

    host.Run();
    复制代码

    1. 查看结果

    2. 代码解析

    a. Host配置

    .ConfigureHostConfiguration(configHost =>

    {

      //配置根目录

      configHost.SetBasePath(Directory.GetCurrentDirectory());

      //读取host的配置json,和appsetting类似,暂不需要先注释掉,可根据需要开启

      //configHost.AddJsonFile("hostsettings.json", true, true);

      //读取环境变量,Asp.Net core默认的环境变量是以ASPNETCORE_作为前缀的,这里也采用此前缀以保持一致

      configHost.AddEnvironmentVariables("ASPNETCORE_");

      //可以在启动host的时候之前可传入参数,暂不需要先注释掉,可根据需要开启

      //configHost.AddCommandLine(args);

    })

    b. App配置

    .ConfigureAppConfiguration((hostContext, configApp) =>

    {

      //读取应用的配置json

      configApp.AddJsonFile("appsettings.json", true);

      //读取应用特定环境下的配置json

      configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true);

      //读取环境变量

      configApp.AddEnvironmentVariables();

      //可以在启动host的时候之前可传入参数,暂不需要先注释掉,可根据需要开启

      //configApp.AddCommandLine(args);

    })

    c. 配置服务及依赖注入注册,注:没有Middleware的配置了。

    .ConfigureServices((hostContext, services) =>
    {

      //添加日志Service
      services.AddLogging();

      //添加Timer Hosted Service
      services.AddHostedService();
    })

    d. 日志配置

    .ConfigureLogging((hostContext, configLogging) =>
    {

      //输出控制台日志
      configLogging.AddConsole();

      //开发环境输出Debug日志
      if (hostContext.HostingEnvironment.EnvironmentName == EnvironmentName.Development)
      {
        configLogging.AddDebug();
      }
    })

    e. 使用控制台生命周期

    .UseConsoleLifetime() //使用Ctrl + C退出

    其它详细的可参考:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.1

    使用Quartz

    1. 添加Host Nuget包。

    Install-Package Quartz -Version 3.0.5
    Install-Package Quartz.Plugins -Version 3.0.5

    1. Quartz配置。

    之前Quartz的配置是放在quartz.config里面的,但我更喜欢使用appsettings.json,因此,把配置改成了从appsettings.json。

    先建一个QuartzOption的类:

    复制代码
    ///


    /// 更多设置请参考:https://github.com/quartznet/quartznet/blob/master/src/Quartz/Impl/StdSchedulerFactory.cs
    ///

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

    }

    public class Scheduler
    {
    public string InstanceName { get; set; }
    }

    public class ThreadPool
    {
    public string Type { get; set; }

    public string ThreadPriority { get; set; }
    
    public int ThreadCount { get; set; }
    

    }

    public class Plugin
    {
    public JobInitializer JobInitializer { get; set; }
    }

    public class JobInitializer
    {
    public string Type { get; set; }
    public string FileNames { get; set; }
    }
    复制代码

    1. 重写JobFactory。

    复制代码
    public class JobFactory : IJobFactory
    {
    private readonly IServiceProvider _serviceProvider;

    public JobFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        var job = _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
        return job;
    }
    
    public void ReturnJob(IJob job)
    {
    }
    

    }
    复制代码

    1. 编写Quartz Hosted Service

    复制代码
    public class QuartzService : IHostedService
    {
    private readonly ILogger _logger;
    private readonly IScheduler _scheduler;

    public QuartzService(ILogger<QuartzService> logger, IScheduler scheduler)
    {
        _logger = logger;
        _scheduler = scheduler;
    }
    
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("开始Quartz调度...");
        await _scheduler.Start(cancellationToken);
    }
    
    public async Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("停止Quartz调度...");
        await _scheduler.Shutdown(cancellationToken);
    }
    

    }
    复制代码

    1. 准备appsettings.json

    复制代码
    {
    "quartz": {
    "scheduler": {
    "instanceName": "HostedService.Quartz"
    },
    "threadPool": {
    "type": "Quartz.Simpl.SimpleThreadPool, Quartz",
    "threadPriority": "Normal",
    "threadCount": 10
    },
    "plugin": {
    "jobInitializer": {
    "type": "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins",
    "fileNames": "quartz_jobs.xml"
    }
    }
    }
    }
    复制代码

    1. 编写一个TestJob

    复制代码
    public class TestJob : IJob
    {
    private readonly ILogger _logger;

    public TestJob(ILogger<TestJob> logger)
    {
        _logger = logger;
    }
    
    public Task Execute(IJobExecutionContext context)
    {
        _logger.LogInformation(string.Format("[{0:yyyy-MM-dd hh:mm:ss:ffffff}]任务执行!", DateTime.Now));
        return Task.CompletedTask;
    }
    

    }
    复制代码

    1. 准备Quartz的调度文件quartz_jobs.xml

    复制代码

    true TestJob TestGroup 测试任务 HostedService.Quartz.Jobs.TestJob, HostedService.Quartz true false TestTrigger TestGroup 测试触发器 TestJob TestGroup -1 2000
    <!--<trigger>
      <cron>
        <name>TestTrigger</name>
        <group>TestGroup</group>
        <description>测试触发器</description>
        <job-name>TestJob</job-name>
        <job-group>TestGroup</job-group>
        <cron-expression>0/2 * * * * ?</cron-expression>
      </cron>
    </trigger>-->
    
    复制代码
    1. 注册Quartz Hosted Service和TestJob

    复制代码
    .ConfigureServices((hostContext, services) =>
    {
    services.AddLogging();
    //services.AddHostedService();

    services.AddSingleton<IJobFactory, JobFactory>();
    services.AddSingleton(provider =>
    {
        var option = new QuartzOption(hostContext.Configuration);
        var sf = new StdSchedulerFactory(option.ToProperties());
        var scheduler = sf.GetScheduler().Result;
        scheduler.JobFactory = provider.GetService<IJobFactory>();
        return scheduler;
    });
    services.AddHostedService<QuartzService>();
    
    services.AddSingleton<TestJob, TestJob>();
    

    })
    复制代码

    1. 查看结果

    2. 补充说明。

    Generic Service默认的环境是Production,如果想使用Development环境,可以在项目属性的Debug页签中添加环境变量来实现。

    源码地址
    https://github.com/ErikXu/.NetCoreTips/tree/master/HostedService.Quartz

    便捷使用
    https://www.nuget.org/packages/Quartz.HostedService/

    https://github.com/ErikXu/Quartz.HostedService

  • 相关阅读:
    Raphael入门
    EXT使用SASS修改主题样式问题
    Hough Transform Performance article List
    about utf8 ...
    computer vision resource
    Asp.net 中配置 CKEditor和CKFinder
    压缩格式 zip与tar.gz 区别
    一系列按钮的弹出提示框借助jq实现
    SQL Server2005 sa登录出错~
    正则表达式整理
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/9252987.html
Copyright © 2020-2023  润新知