以前一直有朋友問,.Net Core + Linux環境有沒有相似Windows服務的東西。實際上是有的,我瞭解的方法有兩種:git
#1 建立一個ASP.Net Core的Web項目(如Web API),而後經過添加中間件(Middleware)的方式來啓動任務;github
#2 建立一個.Net Core的項目,添加Host,Dependency Injection,Configuration等組件,而後經過Main方法或中間件的方式啓動服務。express
可是,上述兩種方法都有點不足,如:json
#1 會把Web的生命週期引進來,但實際上,咱們並不須要Web的功能,如Controller;app
#2 自己是沒有問題的,可是對開發者的要求相對高一點點,須要對.Net Core的各個組成部分都有必定的認識,簡而言之,門檻有一丟丟高。async
.Net Core 2.1推出了一個Generic Host的概念,能夠很好的解決上面兩種方法的不足:ide
至於爲何選擇Quartz來作調度,我想多是由於情懷吧,由於以前是用的TopShelf+Quartz,其實Hangfire也不錯。函數
1. 建立一個控制檯程序。測試
2. 添加Host Nuget包。ui
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
3. 添加一個基於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(); } }
4. 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<TimedHostedService>(); }) .ConfigureLogging((hostContext, configLogging) => { configLogging.AddConsole(); if (hostContext.HostingEnvironment.EnvironmentName == EnvironmentName.Development) { configLogging.AddDebug(); } }) .UseConsoleLifetime() .Build(); host.Run();
5. 查看結果
6. 代碼解析
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<TimedHostedService>();
})
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
1. 添加Host Nuget包。
Install-Package Quartz -Version 3.0.5 Install-Package Quartz.Plugins -Version 3.0.5
2. Quartz配置。
以前Quartz的配置是放在quartz.config裏面的,但我更喜歡使用appsettings.json,所以,把配置改爲了從appsettings.json。
先建一個QuartzOption的類:
/// <summary> /// 更多設置請參考:https://github.com/quartznet/quartznet/blob/master/src/Quartz/Impl/StdSchedulerFactory.cs /// </summary> 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; } }
3. 重寫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) { } }
4. 編寫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); } }
5. 準備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" } } } }
6. 編寫一個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; } }
7. 準備Quartz的調度文件quartz_jobs.xml
<?xml version="1.0" encoding="UTF-8"?> <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"> <processing-directives> <overwrite-existing-data>true</overwrite-existing-data> </processing-directives> <schedule> <job> <name>TestJob</name> <group>TestGroup</group> <description>測試任務</description> <job-type>HostedService.Quartz.Jobs.TestJob, HostedService.Quartz</job-type> <durable>true</durable> <recover>false</recover> </job> <trigger> <simple> <name>TestTrigger</name> <group>TestGroup</group> <description>測試觸發器</description> <job-name>TestJob</job-name> <job-group>TestGroup</job-group> <repeat-count>-1</repeat-count> <repeat-interval>2000</repeat-interval> </simple> </trigger> <!--<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>--> </schedule> </job-scheduling-data>
8. 註冊Quartz Hosted Service和TestJob
.ConfigureServices((hostContext, services) => { services.AddLogging(); //services.AddHostedService<TimedHostedService>(); 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>(); })
9. 查看結果
10. 補充說明。
Generic Service默認的環境是Production,若是想使用Development環境,能夠在項目屬性的Debug頁籤中添加環境變量來實現。
https://github.com/ErikXu/.NetCoreTips/tree/master/HostedService.Quartz