背景

  之前一直有朋友问,.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实现定时任务调度的更多相关文章

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

    背景 之前.net core使用quartz.net时,总感觉非常变扭,百度和谷歌了N久都没解决以下问题,造成代码丑陋,非常不优雅: 1.项目启动时,要立刻恢复执行quartz.net中的任务 2.q ...

  2. .Net Core小技巧 - Swagger适配虚拟目录及二级目录

    前言 随着前后端分离模式与微服务架构的出现,Web API变得越来越重要及普遍.而后出现的网关技术,使开发者更倾向于使用二级/多级目录来暴露Web API,一是暴露的端口更少,方便管理:二是在网关中可 ...

  3. .Net Core小技巧 - 使用Swagger上传文件

    前言 随着前后端分离开发模式的普及,后端人员更多是编写服务端API接口.调用接口实现文件上传是一个常见的功能,同时也需要一个选择文件上传的界面,可以编写前端界面上传,可以使用Postman.curl来 ...

  4. ASP.NET Core小技巧

    设定开发环境为开发模式,呈现具体错误内容 dotnet run启动时,会在环境变量中查找ASPNETCORE_ENVIRONMENT变量的值,如果没有,则默认会当做Production来处理,隐藏错误 ...

  5. EF Core 小技巧:迁移已经应用到数据库,如何进行迁移回退操作?

    场景描述:项目中存在两个迁移 Teacher 和 TeachingPlan ,TeachingPlan 在 Teacher 之后创建,并且已经执行 dotnet ef database update ...

  6. quartz实现定时任务调度

    一. 业务需求: 实际工作中我们一般会遇到这种需求: 使用Ajax技术每隔几秒从缓存或数据库中读取一些数据, 然后再显示在页面上, 眼下有一个比較好的定时调度框架: quartz能够满足我们的需求. ...

  7. 用abp vNext快速开发Quartz.NET定时任务管理界面

    今天这篇文章我将通过实例代码带着大家一步一步通过abp vNext这个asp.net core的快速开发框架来进行Quartz.net定时任务调度的管理界面的开发.大伙最好跟着一起敲一下代码,当然源码 ...

  8. 10个小技巧助您写出高性能的ASP.NET Core代码

    今天这篇文章我们来聊一聊如何提升并优化ASP.NET Core应用程序的性能,本文的大部分内容来自翻译,当然中间穿插着自己的理解,希望对大家有所帮助!话不多说开始今天的主题吧! 我们都知道性能是公共网 ...

  9. IT咨询顾问:一次吐血的项目救火 java或判断优化小技巧 asp.net core Session的测试使用心得 【.NET架构】BIM软件架构02:Web管控平台后台架构 NetCore入门篇:(十一)NetCore项目读取配置文件appsettings.json 使用LINQ生成Where的SQL语句 js_jquery_创建cookie有效期问题_时区问题

    IT咨询顾问:一次吐血的项目救火   年后的一个合作公司上线了一个子业务系统,对接公司内部的单点系统.我收到该公司的技术咨询:项目启动后没有规律的突然无法登录了,重新启动后,登录一断时间后又无法重新登 ...

随机推荐

  1. 构建一个maven聚合类型的横向可扩展项目

    那个时候初入java这个大家庭,学习的方向很乱.毕业后,在公司磨练了一年,总想着是该交一份答卷了,可能成绩不会很好,但求及格!那么考试题目呢,我计划搭建一个横向可扩展的项目,可以在平台自扩展各种子项目 ...

  2. spring IOC与AOP

    Spring IOC容器 spring IOC 容器有两种,分别是 BeanFactory 容器和 ApplicationContext 容器. BeanFactory如下: /*第一步,利用Clas ...

  3. python模拟蒙特·卡罗法计算圆周率

    蒙特·卡罗方法是一种通过概率来得到问题近似解的方法,在很多领域都有重要的应用,其中就包括圆周率近似值的计问题. 假设有一块边长为2的正方形木板,上面画一个单位圆,然后随意往木板上扔飞镖,落点坐标(x, ...

  4. python3 动态import

    有些情况下,需要动态的替换引入的包 1.常用的import方法 import platform import os 2.__import__ 动态引用 loop_manager = __import_ ...

  5. Java四种引用--《深入理解Java虚拟机》学习笔记及个人理解(四)

    Java四种引用--<深入理解Java虚拟机>学习笔记及个人理解(四) 书上P65. StrongReference(强引用) 类似Object obj = new Object() 这类 ...

  6. Angular记录(5)

    文档资料 箭头函数--MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_fun ...

  7. 079、监控利器 sysdig (2019-04-26 周五)

    参考https://www.cnblogs.com/CloudMan6/p/7646995.html   sysdig 是一个轻量级的系统监控工具,同时他还原生支持容器.通过sysdig我们可以近距离 ...

  8. 前端自动提示功能插件-typeahead

    typeahead https://npm.taobao.org/package/npm-typeahead A lightweight web-app that implements typeahe ...

  9. Linux的快捷键一

  10. LaTeX多图合并代码示例(subfigure)

    \usepakage{subfig} \begin{figure*}[!htb] \centering \subfigure[Derm101 data distribution]{\includegr ...