前言

看过我之前博客的人应该都知道,我负责了相当久的部门数据同步相关的工作。其中的艰辛不赘述了。

随着需求的越来越复杂,最近windows的计划任务已经越发的不能满足我了,而且计划任务毕竟太弱智,总是会失败之类,强制结束之类的。

最近增加了一些复杂的参数,每天的任务对同步程序调用需要多次调用不同参数,我也终于打算不再忍受弱智的计划任务。最初测试了一下基于 IIS 的 Quart ,发现还是存在会被回收无法定时的情况,

在此之前我并未做过 Quart 相关的开发。我查了查相关资料,可以更改 IIS 设置修改定时回收的模式,可以通过访问站点来唤醒等,觉得不是很合适。而且综合业务的考虑,实在是没必要在内网客户机搭一个 Web 站点。

这样一来,干脆搞一个 WindowsService 得了,而且定时的场景还是比较常见的,写一份肯定不亏,以后还是用的上。而且也没尝试过基于 Core 写 WindowsService,正好借此机会学习一下。

Worker Service

使用 VS2019 ,安装了 .NET CORE 3.0 以上的 SDK ,安装SDK的时候最好也安装运行时,免得最后忘记。

项目模板自带的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace WorkerServiceTest
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
}
}

Program.cs中,依旧是创建一个 IHost 并启动。为了方便进行依赖注入,可以创建一个 IServiceCollection 的扩展方法来进行相关服务的注册。

而 Worker 类已经提供了一个默认例子,其中有一个 ExecuteAsync 方法,可以直接执行后台任务。这个时候,直接F5就可以正常运行了实现了一个显示当前时间的程序。

Work 类继承 BackgroundService,并重写其 ExecuteAsync 方法。显而易见,ExecuteAsync 方法就是执行后台任务的入口。

Quartz.Net

Quartz.Net 是一个功能齐全的开源作业调度系统,可以在最小规模的应用程序到大型企业系统使用。

Quartz.Net有三个主要概念:

  • job         这是你想要运行的后台任务。
  • trigger     trigger 控制 job 何时运行,通常按某种调度规则触发。
  • scheduler     它负责协调 job 和 trigger,根据 trigger 的要求执行 job。

ASP.NET Core 很好地支持通过 hosted services(托管服务)运行“后台任务”。当你的 ASP.NET Core 应用程序启动,托管服务也启动,并在应用程序的生命周期中在后台运行。

现在有了一个官方包 Quartz.Extensions.Hosting 实现使用 Quartz.Net 运行后台任务,所以把 Quartz.Net 添加到 ASP.NET Core 或 Worker Service 要简单得多。

Quartz.Net 3.2.0 通过 Quartz.Extensions.Hosting 引入了对该模式的直接支持。

Quartz.Extensions.Hosting 即可以用在ASP.NET Core应用程序,也可以用在基于“通用主机”的Worker Service。

虽然可以创建一个“定时”后台服务(例如,每10分钟运行一个任务),但Quartz.NET提供了一个更加健壮的解决方案。

通过使用Cron trigger,你可以确保任务只在一天的特定时间(例如凌晨2:30)运行,或者只在特定的日子运行,或者这些时间的任意组合运行。

Quartz.Net还允许你以集群的方式运行应用程序的多个实例,以便在任何时候只有一个实例可以运行给定的任务。

Quartz.Net托管服务负责Quartz的调度。它将在应用程序的后台运行,检查正在执行的触发器,并在必要时运行相关的作业。

你需要配置调度程序,但不需要担心启动或停止它,IHostedService 会为你管理。

引用 Quartz.Net

你可以通过使用 dotnet add package Quartz.Extensions.Hosting 命令安装 Quartz.Net 包。

如果你查看项目的.csproj,它应该是这样的:

<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>dotnet-QuartzWorkerService-9D4BFFBE-BE06-4490-AE8B-8AF1466778FD</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.3.2" />
</ItemGroup>
</Project>

添加 Quartz.Net 托管服务

注册Quartz.Net需要做两件事:

  1. 注册Quartz.Net需要的DI容器服务。
  2. 注册托管服务。

在 ASP.NET Core 中,通常会在 Startup.ConfigureServices() 方法中完成这两项操作。

但 Worker Services 不使用 Startup 类,所以我们在 Program.cs 中的 IHostBuilder 的 ConfigureServices 方法中注册它们:

    public class Program
{
public static void Main(string[] args)
{
//...
} public static IHostBuilder CreateHostBuilder(string[] args)
{
IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args);
hostBuilder.ConfigureServices((hostContext, services) =>
{
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionScopedJobFactory();
//q.InitJobAndTriggerFromJobsettings(hostContext.Configuration);
}); services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
//services.AddHostedService<Worker>();
}); //Windows
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ hostBuilder.UseWindowsService(); } //Linux
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{ hostBuilder.UseSystemd(); } return hostBuilder;
}
}
}

UseMicrosoftDependencyInjectionScopedJobFactory 告诉 Quartz.NET 注册一个 IJobFactory,该 IJobFactory 通过从DI容器中创建 job。

方法中的 Scoped 部分意味着你的作业可以使用 scoped 服务,而不仅仅是 single 或 transient 服务。

WaitForJobsToComplete 用来设置确保当请求关闭时,Quartz.NET在退出之前优雅地等待作业结束。

如果你现在运行应用程序,将看到Quartz服务启动,并将大量日志转储到控制台,因为篇幅原因,此处不再贴出。

另外 可能有读者注意到了其中的 hostBuilder.UseWindowsService();/hostBuilder.UseSystemd();

这是关于跨平台的一段设置,稍后会有简单讲解。

此时,你已经让 Quartz 作为托管服务在你的应用程序中运行,但是没有任何job让它运行。在下一节中,我们将创建并注册一个简单的job。

创建 Job

因为我的场景是定时运行一个EXE,最常见的通用定时任务场景应该是调用一个接口。

这里举例一个打印日志的Job,我的相关源代码会在结尾处放出。

using Quartz;
using Serilog;
using System;
using System.Threading.Tasks; namespace AX.QuartzServer.Core.Jobs
{
[DisallowConcurrentExecution]
public class TestJob : AXQuartzJob
{
public string Name { get { return "测试Job"; } }
public string Note { get { return "会打印日志"; } } public Task Execute(IJobExecutionContext context)
{
Log.Logger.Information($"{Newtonsoft.Json.JsonConvert.SerializeObject(context.JobDetail.JobDataMap)}");
Log.Logger.Information($"Hello world! {DateTime.Now.ToLongTimeString()}");
return Task.CompletedTask;
}
}
}

这里我使用了全局的 Serilog 来记录日志。所以和一般的日志不太一样。

我还用 [DisallowConcurrentExecution] 属性装饰了 job 。此属性防止Quartz.NET试图同时运行相同的作业。

它将定时的在日志或控制台中打印 Hello world! 和当前时间。

现在我们已经有了作业,我们需要将它与 trigger 一起注册到 DI 容器中。

启动时自动配置Job

Quartz.NET 为运行 job 提供了一些简单的 schedule,但最常见的方法之一是使用 Quartz.NET Cron 表达式,这里不再赘述。

因为我的场景是Windows服务,暂不考虑一些高级的,可以实时停止,注册Job,运行Job之类的封装。

所以决定是在启动时直接通过读取配置文件注册 Job。

下面是注册的关键代码:

    public static class AXQuartzConfigExtensions
{
public static void InitJobAndTriggerFromJobsettings(this IServiceCollectionQuartzConfigurator quartz, IConfiguration configuration)
{
var allJobs = configuration.GetSection("Jobs").Get<List<BaseJobConfig>>(); Log.Logger.Information($"开始注册 Job");
Log.Logger.Information($"共获取到 {allJobs.Count} 个 Job"); foreach (var item in allJobs)
{
Log.Logger.Information($"{JsonConvert.SerializeObject(item)}"); var jobName = $"{item.JobType}_{item.Name}";
var jobKey = new JobKey(jobName);
Log.Logger.Information($"{nameof(jobKey)}_{jobKey}"); var jobData = new JobDataMap();
jobData.PutAll(ToIDictionary(item)); if (item.JobType.ToLower().Contains("testjob"))
{ quartz.AddJob<Jobs.TestJob>(opts => { opts.WithIdentity(jobKey); opts.SetJobData(jobData); }); }
if (item.JobType.ToLower().Contains("windowscmdjob"))
{ quartz.AddJob<Jobs.WindowsCMDJob>(opts => { opts.WithIdentity(jobKey); opts.SetJobData(jobData); }); } quartz.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity($"{jobName}_Trigger")
.WithCronSchedule(item.Cron));
} Log.Logger.Information($"结束注册 Job");
} //...
}

从配置文件中读取了配置之后,为每个 job 创建唯一的 JobKey 。这用于将job与其trggier连接在一起。

用 AddJob 注册我们的 TestJob。它将 TestJob 添加到了 DI 容器中,这样就可以创建它。它还在内部向 Quartz 注册了job。

然后用 AddTrigger 添加触发器,

使用 JobKey 将 trigger 与一个 job 关联起来,并为 trigger 提供唯一的名称(在本例中不是必需的,但如果你在集群模式下运行quartz,这很重要)。

最后,为trigger 设置了 Cron 表达式, Cron 表达式来自配置文件,测试时我用的是每五秒一次。

其中有一些快速实现时未优化的弱智代码之类的,各位读者不用在意。

配置文件配置节:

{
"Logging": {
//...
}
}, //任务配置 DEMO
"JobDemo": {
"Name": "唯一任务名称",
"JobType": "任务类型 windowscmdjob/testjob",
"Cron": "运行时间表达式"
}, "Jobs": [
{
"Name": "LogHelloWorldTest",
"JobType": "testjob",
"Cron": "0 0 */1 * * ?" //这是每小时一次
//"Cron": "0/5 * * * * ?" 这是每五秒一次
}
]
}

如果你现在运行你的应用程序,你会看到和以前一样的启动消息,然后每5秒你会看到HelloWorldJob写入控制台:

这就是搭建一个定时服务的全部关键内容了。

跨平台

在 Host.CreateDefaultBuilder(args) 增加相关环境的调用。

可以使用判断平台的一个函数: IsOSPlatform ,可以判断是否在Windows平台运行,并进行分别调用。

虽然程序可以正常执行,但是还不能正常部署为服务,需要依据平台添加对应的nuget包:
windows服务,需要添加:
Install-Package Microsoft.Extensions.Hosting.WindowsServices
Linux服务,需要添加:
Install-Package Microsoft.Extensions.Hosting.Systemd .UseWindowsService();
.UseSystemd();

Windows下部署

管理员下运行cmd/powershell,执行

sc.exe create WorkerServiceTest binPath=【你编译后的exe路径,不需要带双引号】

提示 CreateService 成功 即安装成功了,可以输入下面的命令运行服务。

sc.exe start WorkerServiceTest

sc.exe负责管理服务,具体配置启动方式和删除,可以查看命令的帮助。另外,友情提醒,如果是在powershell中,不要省略这个.exe,sc有别的用处...

开源代码

https://github.com/aaxuan/AX.QuartzServer(请选择性的忽略其他仓库的垃圾代码 :)

本文将同步发布到个人的语雀博客,欢迎使用语雀的小伙伴相互关注。

https://www.yuque.com/cuxuan

参考

https://devblogs.microsoft.com/aspnet/net-core-workers-as-windows-services/

https://devblogs.microsoft.com/dotnet/net-core-and-systemd/

https://docs.microsoft.com/en-us/dotnet/core/extensions/generic-host

https://dejanstojanovic.net/aspnet/2018/june/setting-up-net-core-servicedaemon-on-linux-os/

https://dotnetcoretutorials.com/2019/12/07/creating-windows-services-in-net-core-part-3-the-net-core-worker-way/

http://www.cnblogs.com/podolski/p/13890572.html

https://www.cnblogs.com/xhy0826/p/Net_Core_Windows_Service_Quartz.html

https://segmentfault.com/a/1190000038753018

基于.Net Core 5.0 Worker Service 的 Quart 服务的更多相关文章

  1. 基于.net core 2.0+mysql+AceAdmin搭建一套快速开发框架

    前言 .net core已经出来一段时间了,相信大家对.net core的概念已经很清楚了,这里就不再赘述.笔者目前也用.net core做过一些项目,并且将以前framework下的一些经验移植到了 ...

  2. 一个基于 .NET Core 2.0 开发的简单易用的快速开发框架 - LinFx

    LinFx 一个基于 .NET Core 2.0 开发的简单易用的快速开发框架,遵循领域驱动设计(DDD)规范约束,提供实现事件驱动.事件回溯.响应式等特性的基础设施.让开发者享受到正真意义的面向对象 ...

  3. .NET Worker Service 作为 Windows 服务运行及优雅退出改进

    上一篇文章我们了解了如何为 Worker Service 添加 Serilog 日志记录,今天我接着介绍一下如何将 Worker Service 作为 Windows 服务运行. 我曾经在前面一篇文章 ...

  4. .NET Core中的Worker Service

    当你想到ASP.NET Core时,可能会想到Web应用程序后端代码,包括MVC和WebAPI.MVC视图和Razor页面还允许使用后端代码生成带有HTML元素的前端UI.全新的Blazor更进一步, ...

  5. HDD成都站:HMS Core 6.0带来新可能 多元服务驱动产品商业成功

    9月10日,由华为开发者联盟主办的HDD(Huawei Developer Day)于成都举行.活动中,华为HMS Core各领域专家重点解读了HMS Core 6.0为开发者带来的多项全新能力,及生 ...

  6. .net core 3.0 Signalr - 07 业务实现-服务端 自定义管理组、用户、连接

    Hub的管理 重写OnConnectedAsync 从连接信息中获取UserId.Groups,ConnectId,并实现这三者的关系,存放于redis中 代码请查看 using CTS.Signal ...

  7. 基于ef core 2.0的数据库增删改审计系统

    1.首先是建审计存储表 CREATE TABLE [dbo].[Audit] ( [Id] [uniqueidentifier] NOT NULL, [EntityName] [nvarchar](1 ...

  8. 在WPF中使用.NET Core 3.0依赖项注入和服务提供程序

    前言 我们都知道.NET Core提供了对依赖项注入的内置支持.我们通常在ASP.NET Core中使用它(从Startup.cs文件中的ConfigureServices方法开始),但是该功能不限于 ...

  9. .NET 中的 Worker Service 入门介绍

    翻译自 Steve Gordon 2020年3月30日的文章 <WHAT ARE .NET WORKER SERVICES?> [1] 随着 .NET Core 3.0 的发布,ASP.N ...

随机推荐

  1. ALGO基础(一)—— 排序

    ALGO基础(一)-- 排序 冒选插希快归堆,以下均为从小到大排 1 冒泡排序 描述: 比较相邻的元素.如果第一个比第二个大,就交换它们两个: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一 ...

  2. 【转】理解Serverless

    理解Serverless No silver bullet. - The Mythical Man-Month 许多年前,我们开发的软件还是C/S(客户端/服务器)和MVC(模型-试图-控制器)的形式 ...

  3. 清除浏览器默认样式的reset.css(转载于reset.css的官方)

    /* http://meyerweb.com/eric/tools/css/reset/ v2.0-modified | 20110126 License: none (public domain) ...

  4. Power BI成功的背后

    Power BI成功的背后 魔力象限 又是一年Gartner数据分析与BI魔力象限报告的发布,Power BI毫无悬念的第一,并且拉开与其他产品的差距越来越大.在Power BI dataflows( ...

  5. 保姆级别学生党安装Clion IDE(面向华师同学)

    保姆级别学生党安装Clion IDE(面向华师同学) 界面UI 废话不多说,直接上图 具备功能 UI美观 (下面会介绍) 基础的代码编写能力 大容量的IDE插件 (下面会介绍) 代码补全,以及搭配Ki ...

  6. React源码 commit阶段详解

    转: React源码 commit阶段详解 点击进入React源码调试仓库. 当render阶段完成后,意味着在内存中构建的workInProgress树所有更新工作已经完成,这包括树中fiber节点 ...

  7. scrapy框架的介绍与安装

    scrapy框架的原理 使用pycharm安装scrapy库 1.打开新建file,然后有个扳手的setings点击进去,如图所示: 2.选择project 然后点击python interprete ...

  8. SpringMVC-02 第一个SpringMVC程序

    SpringMVC-02 第一个SpringMVC程序 第一个SpringMVC程序 配置版 新建一个Moudle , springmvc-02-hello,确定依赖导入进去了 1.配置web.xml ...

  9. 面试官:不会sql优化?出门右转顺便带上门,谢谢

    导读 作为一个后端程序员,数据库这个东西是绕不开的,特别是写sql的能力,如果您参加过多次面试,那么一定会从面试复盘中发现面试官总是会考察到sql优化这个东西. 我在之前的多次面试中最常遇到的一个问题 ...

  10. Fcitx5 上线 FreeBSD

    Fcitx5 上线 FreeBSD textproc/fcitx5textproc/fcitx5-qttextproc/fcitx5-gtktextproc/fcitx5-configtoolchin ...