.net core+topshelf+quartz创建windows定时任务服务
.net core+topshelf+quartz创建windows定时任务服务
准备工作
- 创建.net core 控制台应用程序,这里不做过多介绍
- 添加TopShelf包:TopShelf;
- 添加Quartz包:Quartz、Quartz.Plugins;
- 添加依赖注入包:Microsoft.Extensions.DependencyInjection;
- 添加读取配置文件包:Microsoft.Extensions.Configuration.Json;
- 添加访问数据库包:Microsoft.EntityFrameworkCore;
- 添加日志包:Serilog、Serilog.Sinks.Console、Serilog.Sinks.File
配置Quartz
- 创建appsettings.json文件,右键文件属性,并更改属性为始终复制 内容
{
"quartz": {
"scheduler": {
"instanceName": "Job"
},
"threadPool": {
"type": "Quartz.Simpl.SimpleThreadPool, Quartz",
"threadPriority": "Normal",
"threadCount": 10
},
"plugin": {
"jobInitializer": {
"type": "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins",
"fileNames": "quartz_jobs.xml"
}
}
}
}
- 创建QuartzOption 类
namespace Job
{
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; }
}
}
添加一个Job
namespace Job
{
public class SyncJob : IJob
{
private readonly IService _service;
public SyncJob(IService service)
{
_service = service;
}
public async Task Execute(IJobExecutionContext context)
{
Log.Information("同步开始...");
_service.DoSomeThing();
}
}
}
实现IJobFactory
namespace Job
{
public class JobFactory : IJobFactory
{
protected readonly IServiceProvider Container;
public JobFactory(IServiceProvider container)
{
Container = container;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return Container.GetService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job)
{
(job as IDisposable)?.Dispose();
}
}
}
创建Quartz调度的配置文件 quartz_jobs.xml
文件名与appsetting中的quartz.plugin.jobInitializer.fileNames 保持一致
<?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>SyncJob</name>
<group>SyncGroup</group>
<description>数据同步任务</description>
<job-type>Mille.Job.SyncJob, Mille.Job</job-type>
<durable>true</durable>
<recover>false</recover>
</job>
<trigger>
<cron>
<name>SyncTrigger</name>
<group>SyncGroup</group>
<description>同步触发器</description>
<job-name>SyncDJob</job-name>
<job-group>SyncGroup</job-group>
<!--每晚23:50跑一次,具体参见cron表达式-->
<cron-expression>0 50 23 ? * *</cron-expression>
</cron>
</trigger>
<!--<trigger>
<simple>
<name>SyncTrigger</name>
<group>SyncGroup</group>
<description>数据同步触发器</description>
<job-name>SyncJob</job-name>
<job-group>SyncGroup</job-group>
<repeat-count>-1</repeat-count>
2s跑一次
<repeat-interval>2000</repeat-interval>
</simple>
</trigger>-->
</schedule>
</job-scheduling-data>
添加一个类,此类用户服务启动调用
namespace Job
{
public class SyncService
{
public async Task StartAsync()
{
var provider = RegisterServices();
Scheduler = provider.GetService(typeof(IScheduler)) as IScheduler;
await Scheduler.Start();
Log.Information("Quartz调度已启动...");
}
public async Task StopAsync()
{
await Scheduler.Shutdown();
Log.Information("Quartz调度结束...");
Log.CloseAndFlush();
}
#region Utils
private IScheduler Scheduler { get; set; }
private static ServiceProvider RegisterServices()
{
Log.Information("配置依赖注入...");
var configuration = ReadFromAppSettings();
var services = new ServiceCollection();
#region
services.AddScoped<SyncService>();
services.AddDbContext<DataContext>(opt => opt.UseMySql(configuration.GetConnectionString("ConnStr")));
services.AddScoped<IService,Service>();
#endregion
#region Quartz
Log.Information("配置Quartz...");
services.AddScoped<IJobFactory, JobFactory>();
services.AddSingleton(service =>
{
var option = new QuartzOption(configuration);
var sf = new StdSchedulerFactory(option.ToProperties());
var scheduler = sf.GetScheduler().Result;
scheduler.JobFactory = service.GetService<IJobFactory>();
return scheduler;
});
services.AddScoped<SyncJob>();
//此处不能写成services.AddScoped<IJob,SyncJob>(); 会造成在找不到SyncJob
#endregion
var provider = services.BuildServiceProvider();
return provider;
}
private static IConfigurationRoot ReadFromAppSettings()
{
//读取appsettings.json
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false)
.Build();
}
#endregion
}
}
配置TopShelf
详情参见 https://topshelf.readthedocs.io/en/latest/configuration/quickstart.html
namespace Job
{
public class Program
{
public static void Main(string[] args)
{
InstanceLog();
var rc = HostFactory.Run(x =>
{
x.Service<SyncService>(s =>
{
s.ConstructUsing(name => new SyncService());
s.WhenStarted(async tc => await tc.StartAsync()); //调用此方法前勿有太多操作,会造成服务启动失败
s.WhenStopped(async tc => await tc.StopAsync());
});
x.RunAsLocalSystem();
x.SetDescription("SyncJob Description");
x.SetDisplayName("SyncJob DisplayName");
x.SetServiceName("SyncJob ServiceName");
});
var exitCode = (int)Convert.ChangeType(rc, rc.GetTypeCode());
Environment.ExitCode = exitCode;
}
private static void InstanceLog()
{
//配置Serilog
var template = "{Timestamp:HH:mm:ss} [{Level:u3}] {Message}{NewLine}{Exception}";
Log.Logger = new LoggerConfiguration()
.WriteTo.File(path: "logs/log.txt", outputTemplate: template, rollingInterval: RollingInterval.Day)
.WriteTo.Console(LogEventLevel.Information)
.CreateLogger();
}
}
}
然后在项目文件中加上项目的运行环境相关配置
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>//不同的环境RID不同
</PropertyGroup>
运行
编译项目之后,进入到/bin/Debug/netcoreapp3.0/win7-x64目录,在此处以管理员运行cmd,然后执行 依次Job.exe install 安装服务, Job.exe start 启动服务
如果遇到启动服务时报 1053错误:服务没有及时响应启动或控制请求。检查Start函数调用之前是否还有其他操作,如有,请将这些操作移动到Start调用后执行;本人就是由于在Start之前执行了依赖注入等操作,导致服务启动失败,故写下这篇文章
此文章有部分借鉴其他博主的博客,如有侵权,请联系删除
.net core+topshelf+quartz创建windows定时任务服务的更多相关文章
- 使用.Net Core 2.2创建windows服务
使用.Net Core 2.2创建windows服务 我的环境 win 10 home Visual Studio 2019 v16.1.3 安装有.net core 2.2 创建项目 编辑项目文件 ...
- .NET创建Windows定时任务
创建Windows定时任务教程 1.创建一个控制台应用程序,保证程序正常运行. 2.右键点击我的电脑->点击管理. 3.在计算机管理弹出框->展开计算机管理(本地)->展开系统工具- ...
- 【C#】C#创建Windows Service服务
目录结构: contents structure [+] 创建Windows服务 配置 安装Windows服务 在Visual Studio中调试 常见问题 最近写了一个TCP连接的程序,由于这种通信 ...
- Topshelf便捷创建Windows服务
结合Quartz.net学习,前提已经创建了一个定时任务,可见 <定时调度框架:Quartz.net> (基于配置文件形式) 首先引用Topshelf.dll 自定义服务TestServi ...
- TopShelf框架创建Windows服务作为Remoting的宿主案例:
1.创建服务 using System; using System.Collections.Generic; using System.Linq; using System.Text; using S ...
- 通过TopShelf简单创建windows service
目前很多项目都是B/S架构的,我们经常会用到webapi.MVC等框架,实际项目中可能不仅仅是一些数据的增删改查,需要对数据进行计算,但是将计算逻辑放到api层又会拖累整个项目的运行速度,从而会写一些 ...
- TopShelf+Quartz.net 实现window服务
Quartz.NET官网 TopShelf 网址 代码地址:https://github.com/SeaLee02/ProjectDemo/tree/master/WindowServerDemo ...
- .Net core使用Quartz.Net 实现定时任务
很多情况下,我们需要完成一些定时执行的功能,用很多定时工具,像:hangfire,TimeJob,以及Quartz.net,不过quartz.net 比较精确一些,功能也比较强大,所以我选择了Quar ...
- 如何创建Windows定时任务
我们经常使用电脑,有没有那么一个瞬间想着要是电脑可以每隔一段时间,自动处理一件事情就好了呢? 其实Windows还真有这样的功能,很多软件检测更新就是通过这个方法实现的. 这次我们来做一个简易的喝水提 ...
随机推荐
- Android APP之WebView如何校验SSL证书
Android系统的碎片化很严重,并且手机日期不正确.手机根证书异常.com.google.android.webview BUG等各种原因,都会导致WebViewClient无法访问HTTPS站点. ...
- 7、Topic
Topics In the previous tutorial we improved our logging system. Instead of using a fanout exchange o ...
- unsigned int数据类型最大数
#include <stdio.h> int main() { unsigned , b = ; ) { a++; } printf( ); unsigned ; do { n = n / ...
- JS高阶---闭包应用(自定义JS模块)
[自定义JS模块] [闭包案例] (1)案例1 对应的模块文件 (2)案例2---使用匿名函数 对应的模块文件 案例2分析:因为内部函数引用了外部函数的变量,且存在嵌套关系,所以是闭包,分析结构图如下 ...
- 实战接口开发:python + flask + mysql + redis(根据反馈,持续细化更新。。。)
前言 自动化已经成为测试的必备技能之一了,所以,很多想跳槽的测试朋友都在自学,特别是最实用的接口自动化, 但是很多人因为没有可以练手的项目而苦恼,最终导致缺乏实战经验,其实,完全可以自己开发个简单项目 ...
- 06-人脸识别-MTCNN的感性认识(转载)
找到一个总结很好的blog. 具体训练的过程会在后续的随笔中给出. 转载自: https://blog.csdn.net/lff1208/article/details/77328357 以下是内容: ...
- Dockerfile解析(八)
一.Dockerfile是什么 Dockerfile是用来构建Docker镜像的构建文件,是由一系列命令和参数构成的脚本. 1. 构建的步骤 运行容器:docker run 构建新的镜像:docker ...
- Linux SSH建立连接过程分析
https://blog.csdn.net/qwertyupoiuytr/article/details/71213463 SSH建立连接的过程主要分为下面几个阶段: SSH协议版本协商阶段.SSH目 ...
- Bandit
CSE599:online and adaptive machine learning Lecture 3:Stochastic Multi-Armed Bandits, Regret Minimiz ...
- 41 修改树莓派交换分区 SWAP 的大小
http://blog.lxx1.com/3289 SWAP就是LINUX下的虚拟内存分区,它的作用是在物理内存使用完之后,将磁盘空间(也就是SWAP分区)虚拟成内存来使用.它和Windows系统的交 ...