Quartz.Net 3.0.7.0+Topshelf使用心得
首先nuget下载Topshelf 4.2.1.215和Quartz 3.0.7.0
创建入口
class Program
{
static void Main(string[] args)
{
// 配置和运行宿主服务
HostFactory.Run(x =>
{
x.Service<QuartzServiceRunner>(s =>
{
s.ConstructUsing(name => new QuartzServiceRunner());
s.WhenStarted(m => m.Start());
s.WhenStopped(m => m.Stop());
s.WhenPaused(m => m.Pause());
s.WhenContinued(m => m.Continue());
});
////服务器使用内置账户运行
x.RunAsLocalService();
//// 服务自动启动(延时启动) -- 需要 .NET 4.0 或以上版本
x.StartAutomaticallyDelayed();
// 服务描述信息
x.SetDescription(AppConfigHelper.GetAppSetting("ServiceDescription") ?? "油联网后台服务");
// 服务显示名称
x.SetDisplayName(AppConfigHelper.GetAppSetting("ServiceDisplayName") ?? "油联网后台服务");
// 服务名称
x.SetServiceName(AppConfigHelper.GetAppSetting("ServiceName") ?? "油联网后台服务");
x.EnablePauseAndContinue();
});
Console.ReadKey();
}
}
config配置
<!--topshelf服务配置-->
<add key="ServiceName" value="Demo.Services"/>
<add key="ServiceDescription" value="后台服务Demo"/>
<add key="ServiceDisplayName" value="Demo.Services"/>
开始执行QuartzServiceRunner
RunProgram方法中的线程池和远程输出配置用于之后的作业远程管理
本demo通过xml的方式实现灵活的任务配置调度,该xml为quartz_jobs.xml
public class QuartzServiceRunner
{
//创建调度器工厂
ISchedulerFactory factory = new StdSchedulerFactory();
//创建调度器
IScheduler scheduler = null;
public QuartzServiceRunner()
{
RunProgram().GetAwaiter().GetResult();
}
private async Task RunProgram()
{
var properties = new NameValueCollection();
properties["quartz.scheduler.instanceName"] = "RemoteServerSchedulerClient";
// 设置线程池
properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
properties["quartz.threadPool.threadCount"] = "5";
properties["quartz.threadPool.threadPriority"] = "Normal";
// 远程输出配置
properties["quartz.scheduler.exporter.type"] = "Quartz.Simpl.RemotingSchedulerExporter, Quartz";
properties["quartz.scheduler.exporter.port"] = AppConfigHelper.GetAppSetting("ExporterPort") ?? "82";
properties["quartz.scheduler.exporter.bindName"] = "QuartzScheduler";
properties["quartz.scheduler.exporter.channelType"] = "tcp";
factory = new StdSchedulerFactory(properties);
XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(new SimpleTypeLoadHelper());
scheduler =await factory.GetScheduler();
await processor.ProcessFileAndScheduleJobs("~/quartz_jobs.xml", scheduler);
//myJobListener监控所有的job
scheduler.ListenerManager.AddJobListener(new JobListener(), GroupMatcher<JobKey>.AnyGroup());
}
public async void Start()
{
LogHelper.Info("", "--------------------------------------------------------");
LogHelper.Info("", "服务开始执行");
//开始执行
await scheduler.Start();
}
public async void Stop()
{
LogHelper.Info("", "服务停止执行");
//shutdown(true)表示等待所有正在执行的任务执行完毕后关闭Scheduler
//shutdown(false),即shutdown()表示直接关闭Scheduler
await scheduler.Shutdown(false);
}
public async void Pause()
{
LogHelper.Info("", "服务暂停执行");
//暂停
await scheduler.PauseAll();
}
public async void Continue()
{
LogHelper.Info("", "服务继续执行");
//继续
await scheduler.ResumeAll();
}
}
任务job基类 BaseJob
该基类的主要作用是记录任务的执行记录和异常处理
public abstract class BaseJob : IJob
{
public abstract Task ExecuteJob(IJobExecutionContext context);
public async Task Execute(IJobExecutionContext context)
{
var fullName = context.JobDetail.JobType.FullName;
try
{
LogHelper.Info("", fullName + ":开始执行");
await Console.Out.WriteLineAsync($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}:{fullName}:开始执行");
await ExecuteJob(context);
LogHelper.Info("", fullName + ":执行结束");
await Console.Out.WriteLineAsync($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}:{fullName}:执行结束");
}
catch (Exception ex)
{
JobExecutionException e = new JobExecutionException(ex);
await Console.Out.WriteLineAsync(fullName + ":" + e.Message);
LogHelper.Error("", fullName + ":" + e.Message);
// true 表示立即重新执行作业
// e.RefireImmediately = false;
//true 表示 Quartz 会自动取消所有与这个 job 有关的 trigger,从而避免再次运行 job
e.UnscheduleAllTriggers = true;
LogHelper.Info("", $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}:{fullName}:将停止执行");
new WechatWorkApiBLL().SendLogs($"{fullName}将停止执行,错误详情:{e.Message}");
throw e;
}
}
}
quartz_jobs.xml配置
cron表达式可在http://www.bejson.com/othertools/cron/查询或生成
<?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>
<!--Demo-->
<!-- job 作业节点,每个作业则对应一个 job 节点。-->
<job>
<!--作业名称,同一个 group 中作业名称不能相同。-->
<name>DemoJob</name>
<!--作业分组名称,表示该作业所属分组。-->
<group>DemoJobGroup</group>
<!-- 作业描述,用于描述该作业的具体功能。-->
<description>DemoJobjob</description>
<!-- 指定作业将调用的作业实现类,格式为:命名空间.类名,程序集名称-->
<job-type>HSIOT.Basic.QuartzService.App_Jobs.DemoJob,HSIOT.Basic.QuartzService</job-type>
<!--默认为true-->
<durable>true</durable>
<!--默认为false-->
<recover>false</recover>
</job>
<!--trigger 作业触发器节点,用于定义指定的作业以何种方式触发,一个作业可以有多个触发器,而每个触发器都独立执行调度。-->
<trigger>
<!--cron复杂任务触发器,使用cron表达式定制任务调度-->
<cron>
<!--触发器名称-->
<name>DemoTrigger</name>
<!-- 触发器分组名称-->
<group>DemoGroup</group>
<!-- 要调度的作业名称-->
<job-name>DemoJob</job-name>
<!--要调度的作业分组名称-->
<job-group>DemoJobGroup</job-group>
<!--cron 表达式 ;该节点为必须,如果省略整个服务将不能正常运行!可以在 http://www.bejson.com/othertools/cron/ 生成或查询-->
<cron-expression>0/10 * * * * ? </cron-expression>
<!--每10秒运行一次-->
</cron>
</trigger>
</schedule>
</job-scheduling-data>
DemoJob
public class DemoJob : BaseJob
{
public override async Task ExecuteJob(IJobExecutionContext context)
{
await Console.Out.WriteLineAsync($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}:Hello QuartzNet...");
}
}
运行截图

TopShelf服务安装
进入 Debug 文件夹,找到程序的.exe文件,管理员身份启动命令窗口,Demo.Services.exe install命令,即可发布该服务到本地

浙公网安备 33010602011771号