前言

以前总结过一篇基于Quartz+Topshelf+.netcore实现定时任务Windows服务 https://www.cnblogs.com/gt1987/p/11806053.html。回顾起来发现有点野路子的感觉,没有使用.netcore推荐的基于 HostedService 的方式,也没有体现.net core跨平台的风格。于是重新写了一个Sample。

Work Service

首先搭建项目框架。

  • 版本 .netcore3.1
  • 建立一个Console程序项目模板,修改 project 属性为 <Project Sdk="Microsoft.NET.Sdk.Worker">
  • Nuget引入 Microsoft.Extensions.Hosting,支持配置+Logging+注入等基本框架内容。
  • Nuget引入 Quartz.Jobs 组件。

后来发现vs2019实际有一个 Worker Service 项目模板,直接选择建立即可,不用上面这么麻烦~~

QuartzJob、HostedService集成

集成的主要思路为以 HostedService 作为服务承载,启动的时候 加载 QuartzJob 定时任务配置并启动。而 HostedService 则自动接入.netcore服务程序体系。

由于 QuartzJob 暂没有专门的.netcore版本,这里我们首先要作下特别处理,实现一个JobFactory用于集成.net core依赖注入框架。

public class MyJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider; public MyJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
} public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
} public void ReturnJob(IJob job)
{
//IJob已经在.net core容器体系下,应该考虑通过DI的方式 dispose
//IJob对象的销毁 if implement IDisposable
//var dispose = job as IDisposable;
//dispose?.Dispose();
}
}

定义一个SampleJob:

[DisallowConcurrentExecution]
public class SampleJob : IJob
{
private readonly ILogger<SampleJob> _logger; public SampleJob(ILogger<SampleJob> logger)
{
_logger = logger;
} public void Dispose()
{
_logger.LogInformation($"{nameof(SampleJob)} disposed.");
} public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation($"{nameof(SampleJob)} executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
await Task.CompletedTask;
}
}

定义自己的HostedService,在Start方法里面配置了定时任务并启动

public class QuartzJobHostedService : IHostedService
{
private readonly IScheduler _scheduler;
private readonly ILogger<QuartzJobHostedService> _logger; public QuartzJobHostedService(IScheduler scheduler,
ILogger<QuartzJobHostedService> logger)
{
_scheduler = scheduler;
_logger = logger;
} public async Task StartAsync(CancellationToken cancellationToken)
{
//just for sample test,should configuration in config file when dev
//sample job
var job = CreateJob(typeof(SampleJob));
var trigger = CreateTrigger("SampleJob", "0/5 * * * * ?");
await _scheduler.ScheduleJob(job, trigger, cancellationToken); //disposed job
var job2 = CreateJob(typeof(DisposedSampleJob));
var trigger2 = CreateTrigger("DisposeSampleJob", "0/10 * * * * ?");
await _scheduler.ScheduleJob(job2, trigger2, cancellationToken); await _scheduler.Start(cancellationToken); _logger.LogInformation("jobScheduler started.");
} public async Task StopAsync(CancellationToken cancellationToken)
{
await _scheduler?.Shutdown(cancellationToken); _logger.LogInformation("jobScheduler stoped.");
} private ITrigger CreateTrigger(string name, string cronExpression)
{
return TriggerBuilder
.Create()
.WithIdentity($"{name}.trigger")
.WithCronSchedule(cronExpression)
.Build();
} private IJobDetail CreateJob(Type jobType)
{
return JobBuilder
.Create(jobType)
.WithIdentity(jobType.FullName)
.WithDescription(jobType.Name)
.Build();
}
}

最后我们看一下服务的启动配置,注意IScheduler和IJobFactory的生命周期,这里由于 StdSchedulerFactory.GetDefaultScheduler() 的原因,必须是Singleton来兼容。

class Program
{
static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
host.Run();
} public static IHostBuilder CreateHostBuilder(string[] args) =>
Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
.ConfigureLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
loggingBuilder.SetMinimumLevel(LogLevel.Information);
})
.ConfigureServices((context, services) =>
{
services.AddTransient<SampleJob>()
.AddTransient<DisposedSampleJob>()
.AddTransient<IDisposableService, DisposableService>()
.AddSingleton<IJobFactory, MyJobFactory>()
.AddSingleton<IScheduler>(sp =>
{
var scheduler = StdSchedulerFactory.GetDefaultScheduler().ConfigureAwait(false).GetAwaiter().GetResult();
scheduler.JobFactory = sp.GetRequiredService<IJobFactory>();
return scheduler;
})
.AddHostedService<QuartzJobHostedService>();
})
//if install by topshelf,don't need this
.UseWindowsService(); }

这样一个简单的定时任务服务就搭建完成了,可以本地启动运行。但是如果要部署到服务器上作为windows服务或者linux服务,还需要作一点额外的工作。

IDisposable 问题

这里插入另外一个话题,我们看到 IJobFactory 有一个 ReturnJob 方法用于处理Job对象的资源释放问题。但是我们知道在.netcore依赖注入体系下,任何通过注入获取的对象一定不能通过自己手动方式来处理资源释放问题。参考官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#design-services-for-dependency-injection。那么如何处理需要手动释放例如 IDisposable 的问题呢?

这里我们建立一个继承 IDisposable 接口服务

//通常情况下 不应该将IDisposebale接口 注册为 Transient or Scope。改用工厂模式创建
public interface IDisposableService : IDisposable
{
} public class DisposableService : IDisposableService
{
private ILogger<DisposableService> _logger; public DisposableService(ILogger<DisposableService> logger)
{
_logger = logger;
} public void Dispose()
{
_logger.LogInformation($"{nameof(DisposableService)} has disposed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}.");
}
}

依赖IDisposableService的SampleJob:

/// <summary>
/// 需要dispose
/// 1.IDisposableService可以注册为Singleton,会自动dispose
/// 2.如果不能注册单例,则如本例方式通过IServiceProvider.CreateScope方式处理
/// </summary>
[DisallowConcurrentExecution]
public class DisposedSampleJob : IJob
{
private readonly ILogger<DisposedSampleJob> _logger;
private readonly IServiceProvider _serviceProvider; public DisposedSampleJob(ILogger<DisposedSampleJob> logger,
IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
} public async Task Execute(IJobExecutionContext context)
{
//if IDisposableService register Transient,use CreateScope to dispose IDisposableService
//if IDisposableService register singleton,it can be inject directly and dispose automatically
using (var scope = _serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetRequiredService<IDisposableService>();
_logger.LogInformation($"{nameof(DisposedSampleJob)} executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}.");
await Task.CompletedTask;
}
}
}

这里如注释,要么将 IDisposableService 注册为单例,直接注入引用,.net core DI框架会自动处理资源释放。如果某些原因不能注册单例,则需要采取不太推荐的 定位器模式,如上面代码中实现方式来处理。

部署windows服务

如果要部署到windows服务,则还需要引入 Microsoft.Extensions.Hosting.WindowsServices组件,在构建 IHostBuilder的时候加入 UseWindowsService()即可。它主要的功能是将整个系统的生命周期接入windows服务的生命周期。(默认的是console控制台程序生命周期)。

然后就是烦人的部署到windows服务。这里提供了install和uninstall2个脚本,使用的是window sc工具。

install.bat

set serviceName=QuartzJob.Sample.JobService
set serviceFilePath=F:\gt_work\MyProject\git_project\gt.SomeSamples\QuartzJob.Sample\bin\Release\netcoreapp3.1\QuartzJob.Sample.exe
set serviceDescription=sample job sc create %serviceName% BinPath=%serviceFilePath%
sc config %serviceName% start=auto
sc description %serviceName% %serviceDescription%
sc start %serviceName%
pause

uninstall.bat

set serviceName=QuartzJob.Sample.JobService

sc stop   %serviceName%
sc delete %serviceName% pause

通过管理员权限启动即可正常部署和卸载windows服务

部署Linux服务

如果要部署到Linux服务的话,我查到的有两种方式,一种是Systemd方式,需要引入 Microsoft.Extensions.Hosting.Systemd组件,加入 UseSystemd。另一种方式使用SuperVisor来创建服务。这里我尝试使用了SuperVisor来实现。

我的Linux版本是Centos7,这里刚开始我找了一台Centos6的机器,在安装.netcore sdk这步就走不下去了,这里注意下。

  • 注册microsoft密钥 sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm

  • 安装.netcore sudo yum install dotnet-sdk-3.1

  • 安装SuperVisor yum install -y supervisor,这里也许要安装依赖 yum install epel-release

  • 配置启动,在/etc/supervisord.d/ 新建 QuartzJob.Sample.ini 配置文件。directory指向到发布包目录。

      [program:QuartzJob.Sample]
    command=dotnet QuartzJob.Sample.dll
    directory=~/gt/QuartzJob.Sample
    environment=ASPNETCORE__ENVIRONMENT=Production
    user=root
    stopsignal=INT
    autostart=false
    autorestart=false
    startsecs=1
    stderr_logfile=/var/log/quartzJob.err.log
    stdout_logfile=/var/log/quartzJob.out.log

这么做的原因可以查看 /etc/supervisord.conf 配置中这一段 files=supervisord.d/*ini,表示默认加载启动supervisord.d目录下的 .ini文件配置

  • 启动SuperVisor,sudo service supervisord start。服务正常启动

集成Topshelf

Topshelf组件在framework时代是一款非常方便生成服务windows服务的工具,可以通过代码的方式配置并生成windows服务。可惜目前没有看到.netcore的配合版本。且由于依赖windows系统,似乎不太切合.netcore跨平台的特性。这里给出集成的方式,只适用windows平台。

    static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build(); HostFactory.Run(x =>
{
x.Service<IHost>(s =>
{
s.ConstructUsing(n => host);
s.WhenStarted(tc => tc.StartAsync());
s.WhenStopped(tc => tc.StopAsync().Wait());
});
x.RunAsLocalSystem(); x.SetDisplayName("Quartz.Sample.JobService");
x.SetServiceName("Quartz.Sample.JobService");
});
}

install 命令:

  • .\QuartzJob.Sample.exe install
  • .\QuartzJob.Sample.exe start

uninstall 命令:

  • .\QuartzJob.Sample.exe stop
  • .\QuartzJob.Sample.exe uninstall

这里的原理就是用Topshelf的Host替代.netcore的Host,在Topshelf Host启动时再启动.netcore Host。反正看着很变扭。

另外特别注意 s.WhenStarted(tc => tc.StartAsync()); 这里使用的是StartAsync方法而不是Start方法,因为Start方法是同步堵塞的,在部署到windows服务时,由于这一步堵塞,会导致windows服务一直卡在启动状态直至超时启动失败。

.NET CORE QuartzJob定时任务+Windows/Linux部署的更多相关文章

  1. .NET Core Generic Host Windows服务部署使用Topshelf

    此文源于前公司在迁移项目到.NET Core的过程中,希望使用Generic Host来管理定时任务程序时,没法部署到Windows服务的问题,而且官方也没给出解决方案,只能关注一下官方issue # ...

  2. windows/Linux下设置ASP.Net Core开发环境并部署应用

    10分钟学会在windows/Linux下设置ASP.Net Core开发环境并部署应用 创建和开发ASP.NET Core应用可以有二种方式:最简单的方式是通过Visual Studio 2017 ...

  3. linux centos7 和 windows下 部署 .net core 2.0 web应用

    centos7 下部署asp.net core 2.0应用 安装CentOS7 配置网络[可选] 安装.Net core2.0 创建测试Asp.net Core应用程序 正式部署项目 安装VMware ...

  4. Windows 服务器部署 asp.net core

    踩坑日记与 Windows 服务器部署 asp.net core 指南. 准备 操作系统:Windows Server 2008 R2 或更高版本 文件: Microsoft Visual C++ 2 ...

  5. .Net Core Linux部署

    .Net Core是微软最新的开源框架跨平台框架 官网文档 .Net Core相关发布指令,以及发布RId便于查看 RID链接 .Net Core要想发布到Linux有俩种方案,分别是依赖框架的部署( ...

  6. [亲测]ASP.NET Core 2.0怎么发布/部署到Ubuntu Linux服务器并配置Nginx反向代理实现域名访问

    前言 ASP.NET Core 2.0 怎么发布到Ubuntu服务器?又如何在服务器上配置使用ASP.NET Core网站绑定到指定的域名,让外网用户可以访问呢? 步骤 第1步:准备工作 一台Liun ...

  7. .NET Core多平台开发体验[3]: Linux (Windows Linux子系统)

    如果想体验Linux环境下开发和运行.NET Core应用,我们有多种选择.一种就是在一台物理机上安装原生的Linux,我们可以根据自身的喜好选择某种Linux Distribution,目前来说像R ...

  8. [亲测]七步学会ASP.NET Core 2.0怎么发布/部署到Ubuntu Linux服务器并配置Nginx反向代理实现域名访问

    前言 ASP.NET Core 2.0 怎么发布到Ubuntu服务器?又如何在服务器上配置使用ASP.NET Core网站绑定到指定的域名,让外网用户可以访问呢? 步骤 第1步:准备工作 一台Liun ...

  9. 怎么部署 .NET Core Web项目 到linux

    .NET Core is free, open source, cross platform and runs basically everywhere. STEP 0 - GET A CHEAP H ...

随机推荐

  1. Python-TypeError: not all arguments converted during string formatting

    Where? 运行Python程序,报错出现在这一行 return "Unknow Object of %s" % value Why? %s 表示把 value变量装换为字符串, ...

  2. python3 读取写入excel操作-win32com

    前面有写一篇是用xlrd操作excel的,这一篇是使用win32com来进行操作excel,个人推荐使用win32com. 要使用win32com组件,也需要先导入win32com包. # -*- c ...

  3. MyBatis 进阶,MyBatis-Plus!(基于 Springboot 演示)

    这一篇从一个入门的基本体验介绍,再到对于 CRUD 的一个详细介绍,在介绍过程中将涉及到的一些问题,例如逐渐策略,自动填充,乐观锁等内容说了一下,只选了一些重要的内容,还有一些没提及到,具体可以参考官 ...

  4. 【Python】数据结构

    列表的更多特性 list.append(x) 在列表的末尾添加一个元素.相当于 a[len(a):] = [x] . list.extend(iterable) 使用可迭代对象中的所有元素来扩展列表. ...

  5. pytest之将多个测试用例放在一个类中,生成唯一临时文件夹

    将多个测试用例放在一个类中 简单来说就是将多个测试用例放到类中,通过pytest去管理,这和Testng很像.示例代码如下: """ 将多个测试用例放到一个类中执行 &q ...

  6. 实验 5:OpenFlow 协议分析和 OpenDaylight 安装

    一.实验目的 回顾 JDK 安装配置,了解 OpenDaylight 控制的安装,以及 Mininet 如何连接;通过抓包获取 OpenFlow 协议,验证 OpenFlow 协议和版本,了解协议内容 ...

  7. c语言 static的用法

    static在c里面可以用来修饰变量,也可以用来修饰函数.先看用来修饰变量的时候.变量在c里面可分为存在全局数据区.栈和堆里.其实我们平时所说的堆栈是栈而不是堆,不要弄混.int a ;int mai ...

  8. 【题解】[SDOI2016]征途

    Link 题目大意:给定序列,将它划分为\(m\)段使得方差最小,输出\(s^2*m^2\)(一个整数). \(\text{Solution:}\) 这题我通过题解中的大佬博客学到了一般化方差柿子的写 ...

  9. Oracle 数据库下赋予用户的执行存储过程和创建表权限

    grant create any table to username; grant create any procedure to username; grant execute any proced ...

  10. 用React 中的useState改变值不重新渲染的问题

    不渲染 const [lists,setLists] =useState([]); ..... const arr = lists; arr.splice(index,1) //根据删除index下标 ...