.Net Core小技巧 - Hosted Services + Quartz实现定时任务调度
背景
之前一直有朋友问,.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
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()); 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, ); 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
使用Quartz
1. 添加Host Nuget包。
Install-Package Quartz -Version 3.0.
Install-Package Quartz.Plugins -Version 3.0.
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
便捷使用
https://www.nuget.org/packages/Quartz.HostedService/
https://github.com/ErikXu/Quartz.HostedService
.Net Core小技巧 - Hosted Services + Quartz实现定时任务调度的更多相关文章
- Hosted Services+Quartz实现定时任务调度
背景 之前.net core使用quartz.net时,总感觉非常变扭,百度和谷歌了N久都没解决以下问题,造成代码丑陋,非常不优雅: 1.项目启动时,要立刻恢复执行quartz.net中的任务 2.q ...
- .Net Core小技巧 - Swagger适配虚拟目录及二级目录
前言 随着前后端分离模式与微服务架构的出现,Web API变得越来越重要及普遍.而后出现的网关技术,使开发者更倾向于使用二级/多级目录来暴露Web API,一是暴露的端口更少,方便管理:二是在网关中可 ...
- .Net Core小技巧 - 使用Swagger上传文件
前言 随着前后端分离开发模式的普及,后端人员更多是编写服务端API接口.调用接口实现文件上传是一个常见的功能,同时也需要一个选择文件上传的界面,可以编写前端界面上传,可以使用Postman.curl来 ...
- ASP.NET Core小技巧
设定开发环境为开发模式,呈现具体错误内容 dotnet run启动时,会在环境变量中查找ASPNETCORE_ENVIRONMENT变量的值,如果没有,则默认会当做Production来处理,隐藏错误 ...
- EF Core 小技巧:迁移已经应用到数据库,如何进行迁移回退操作?
场景描述:项目中存在两个迁移 Teacher 和 TeachingPlan ,TeachingPlan 在 Teacher 之后创建,并且已经执行 dotnet ef database update ...
- quartz实现定时任务调度
一. 业务需求: 实际工作中我们一般会遇到这种需求: 使用Ajax技术每隔几秒从缓存或数据库中读取一些数据, 然后再显示在页面上, 眼下有一个比較好的定时调度框架: quartz能够满足我们的需求. ...
- 用abp vNext快速开发Quartz.NET定时任务管理界面
今天这篇文章我将通过实例代码带着大家一步一步通过abp vNext这个asp.net core的快速开发框架来进行Quartz.net定时任务调度的管理界面的开发.大伙最好跟着一起敲一下代码,当然源码 ...
- 10个小技巧助您写出高性能的ASP.NET Core代码
今天这篇文章我们来聊一聊如何提升并优化ASP.NET Core应用程序的性能,本文的大部分内容来自翻译,当然中间穿插着自己的理解,希望对大家有所帮助!话不多说开始今天的主题吧! 我们都知道性能是公共网 ...
- IT咨询顾问:一次吐血的项目救火 java或判断优化小技巧 asp.net core Session的测试使用心得 【.NET架构】BIM软件架构02:Web管控平台后台架构 NetCore入门篇:(十一)NetCore项目读取配置文件appsettings.json 使用LINQ生成Where的SQL语句 js_jquery_创建cookie有效期问题_时区问题
IT咨询顾问:一次吐血的项目救火 年后的一个合作公司上线了一个子业务系统,对接公司内部的单点系统.我收到该公司的技术咨询:项目启动后没有规律的突然无法登录了,重新启动后,登录一断时间后又无法重新登 ...
随机推荐
- CF_528D
一句话题意 给你两个串s.t,长度为n.m,字符集为"ATGC",当且仅 当[i - k; i + k]中存在一个j,使得s[j ] = t[x]时,s[i ]可以 和t[x]匹配 ...
- Promise的源码实现(完美符合Promise/A+规范)
Promise是前端面试中的高频问题,我作为面试官的时候,问Promise的概率超过90%,据我所知,大多数公司,都会问一些关于Promise的问题.如果你能根据PromiseA+的规范,写出符合规范 ...
- Ansible 插件 操作介绍
一.Ansible 插件 之 [统计任务处理时间] 在做性能优化之前首先需要做的是收集一些统计数据,这样才能为后面做的性能优化提供数据支持,对比优化前后的结果.非常不错的是,在 github 发现一个 ...
- Elasticsearch 快速开始
Elasticsearch 是一个分布式的 RESTful 风格的搜索和数据分析引擎. 查询 : Elasticsearch 允许执行和合并多种类型的搜索 - 结构化.非结构化.地理位置.度量指标 - ...
- EF Core系列
一. 二. 三. 系列章节 第一节:EF Core简介和CodeFirst和DBFirst两种映射模式(以SQLite和SQLServer为例) 第X节:XXXXXXXXXXXXXXXXXXXXXXX ...
- 使用Ueditor编辑器上传图片总结;
今天使用Ueditor编辑器上传图片一直出问题,在网上找了多种方法,最后总结如下: Ueditor编辑器是百度开发的编辑器,要在jsp页面添加Ueditor编辑器,需要以下几步: (1)到 http: ...
- RAID 划分
RAID0:N块盘组成,逻辑容量为N块盘容量之和:RAID1:两块盘组成,逻辑容量为一块盘容量:RAID3:N+1块盘组成,逻辑容量为N块盘容量之和:RAID5:N块盘组成,逻辑容量为N-1块盘容量之 ...
- jQuery中哪几种选择器
基本选择器:直接根据id,css类名,元素名返回dom元素: 层次选择器:也叫路径选择器: $("div span") 选取<div>里的所有<span>元 ...
- java字符串转为Map类型:split()方法的应用
方法一: package com.thinkgem.jeesite.modules.socketTest.demo2; import java.util.HashMap; import java.ut ...
- vo类,model类,dto类的作用及划分
1.entity里的每一个字段,与数据库相对应, 2.dto里的每一个字段,是和你前台页面相对应, 3.VO,这是用来转换从entity到dto,或者从dto到entity的中间的东西. 举个例子 ...