quartz3.0.7和topshelf4.2.1实现任务调度

Posted bmyblogs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了quartz3.0.7和topshelf4.2.1实现任务调度相关的知识,希望对你有一定的参考价值。

我们都知道quartz是实现任务调度的,也就是定时计划,topshelf是创建windows服务。网上看过很多的例子,都不够完整,于是自己去翻了一遍quart.Net的源码,现在我就来根据quartz.net的源码例子,简单改造一下,基本上也就符合我们的工作使用了,同时quartz.Net还支持集群,可看文章末尾转至quartz.net进行参考。

一、创建一个控制台程序

技术图片

 二、引入相关插件

 引入以下插件,可以通过管理NuGet程序包添加,我是通过程序包控制台添加的,如下:

PM> install-package quartz (安装这个时候同时会安装: quartz.job) 

PM> install-package quartz.plugins

PM> install-package topshelf

 

我们还会使用到日志相关的,原官方的例子是用log4net, 我这里改了一下就用serilog

PM> install-package serilog

PM> install-package serilog.sinks.file (写入日志到文件)

PM> install-package serilog.sinks.console (输出日志到控制台)

 

技术图片

 三、复制相关文件到新建的项目中

1.   Configuration.cs 

2.  IQuartzServer.cs

3.  QuartzServer.cs

4.  QuartzServerFactory.cs

5.  quartz.config

6.  quartz_jobs.xml

7.  SampleJob.cs  (实现了IJob接口)

8.  LoggerProvider.cs (serilog日志相关)

9.  Test.Production.config (单独的配置文件)

技术图片

 

四、代码部分

技术图片
/// <summary>
    /// Configuration for the Quartz server.
    /// </summary>
    public class Configuration
    {
        //private static readonly ILog log = LogManager.GetLogger(typeof(Configuration));

        private const string PrefixServerConfiguration = "QuartzNetWindowsService";
        private const string KeyServiceName = PrefixServerConfiguration + ".serviceName";
        private const string KeyServiceDisplayName = PrefixServerConfiguration + ".serviceDisplayName";
        private const string KeyServiceDescription = PrefixServerConfiguration + ".serviceDescription";
        private const string KeyServerImplementationType = PrefixServerConfiguration + ".type";
        
        private const string DefaultServiceName = "TestService";
        private const string DefaultServiceDisplayName = "TestService";
        private const string DefaultServiceDescription = "Quartz Job Scheduling Server";
        private static readonly string DefaultServerImplementationType = typeof(QuartzServer).AssemblyQualifiedName;

        private static readonly NameValueCollection configuration;

        /// <summary>
        /// Initializes the <see cref="Configuration"/> class.
        /// </summary>
        static Configuration()
        {
            try
            {
                configuration = (NameValueCollection) ConfigurationManager.GetSection("quartz");
            }
            catch (Exception e)
            {
                //log.Warn("could not read configuration using ConfigurationManager.GetSection: " + e.Message);
            }
        }

        /// <summary>
        /// Gets the name of the service.
        /// </summary>
        /// <value>The name of the service.</value>
        public static string ServiceName => GetConfigurationOrDefault(KeyServiceName, DefaultServiceName);

        /// <summary>
        /// Gets the display name of the service.
        /// </summary>
        /// <value>The display name of the service.</value>
        public static string ServiceDisplayName => GetConfigurationOrDefault(KeyServiceDisplayName, DefaultServiceDisplayName);

        /// <summary>
        /// Gets the service description.
        /// </summary>
        /// <value>The service description.</value>
        public static string ServiceDescription => GetConfigurationOrDefault(KeyServiceDescription, DefaultServiceDescription);

        /// <summary>
        /// Gets the type name of the server implementation.
        /// </summary>
        /// <value>The type of the server implementation.</value>
        public static string ServerImplementationType => GetConfigurationOrDefault(KeyServerImplementationType, DefaultServerImplementationType);

        /// <summary>
        /// Returns configuration value with given key. If configuration
        /// for the does not exists, return the default value.
        /// </summary>
        /// <param name="configurationKey">Key to read configuration with.</param>
        /// <param name="defaultValue">Default value to return if configuration is not found</param>
        /// <returns>The configuration value.</returns>
        private static string GetConfigurationOrDefault(string configurationKey, string defaultValue)
        {
            string retValue = null;
            if (configuration != null)
            {
                retValue = configuration[configurationKey];
            }

            if (retValue == null || retValue.Trim().Length == 0)
            {
                retValue = defaultValue;
            }
            return retValue;
        }
    }
}
代码:Configuration.cs
技术图片
/// <summary>
    /// Service interface for core Quartz.NET server.
    /// </summary>
    public interface IQuartzServer
    {
        /// <summary>
        /// Initializes the instance of <see cref="IQuartzServer"/>.
        /// Initialization will only be called once in server‘s lifetime.
        /// </summary>
        Task Initialize();

        /// <summary>
        /// Starts this instance.
        /// </summary>
        void Start();

        /// <summary>
        /// Stops this instance.
        /// </summary>
        void Stop();

        /// <summary>
        /// Pauses all activity in scheduler.
        /// </summary>
        void Pause();

        /// <summary>
        /// Resumes all activity in server.
        /// </summary>
        void Resume();
    }
}
代码:IQuartzServer.cs
技术图片
/// <summary>
    /// The main server logic.
    /// </summary>
    public class QuartzServer : ServiceControl, IQuartzServer
    {
    //    private readonly ILog logger;
        private ISchedulerFactory schedulerFactory;
        private IScheduler scheduler;

        /// <summary>
        /// Initializes a new instance of the <see cref="QuartzServer"/> class.
        /// </summary>
        public QuartzServer()
        {
         //   logger = LogManager.GetLogger(GetType());
        }

        /// <summary>
        /// Initializes the instance of the <see cref="QuartzServer"/> class.
        /// </summary>
        public virtual async Task Initialize()
        {
            try
            {
                schedulerFactory = CreateSchedulerFactory();
                scheduler = await GetScheduler().ConfigureAwait(false);
            }
            catch (Exception e)
            {
              //  logger.Error("Server initialization failed:" + e.Message, e);
                throw;
            }
        }

        /// <summary>
        /// Gets the scheduler with which this server should operate with.
        /// </summary>
        /// <returns></returns>
        protected virtual Task<IScheduler> GetScheduler()
        {
            return schedulerFactory.GetScheduler();
        }

        /// <summary>
        /// Returns the current scheduler instance (usually created in <see cref="Initialize" />
        /// using the <see cref="GetScheduler" /> method).
        /// </summary>
        protected virtual IScheduler Scheduler => scheduler;

        /// <summary>
        /// Creates the scheduler factory that will be the factory
        /// for all schedulers on this instance.
        /// </summary>
        /// <returns></returns>
        protected virtual ISchedulerFactory CreateSchedulerFactory()
        {
            return new StdSchedulerFactory();
        }

        /// <summary>
        /// Starts this instance, delegates to scheduler.
        /// </summary>
        public virtual void Start()
        {
            try
            {
                scheduler.Start();

                //// define the job and tie it to our HelloJob class
                //IJobDetail job = JobBuilder.Create<HelloJob>()
                //    .WithIdentity("job1", "group1")
                //    .Build();

            }
            catch (Exception ex)
            {
              //  logger.Fatal($"Scheduler start failed: {ex.Message}", ex);
                throw;
            }

            //logger.Info("Scheduler started successfully");
        }

        /// <summary>
        /// Stops this instance, delegates to scheduler.
        /// </summary>
        public virtual void Stop()
        {
            try
            {
                scheduler.Shutdown(true);
            }
            catch (Exception ex)
            {
              //  logger.Error($"Scheduler stop failed: {ex.Message}", ex);
                throw;
            }

            //logger.Info("Scheduler shutdown complete");
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public virtual void Dispose()
        {
            // no-op for now
        }

        /// <summary>
        /// Pauses all activity in scheduler.
        /// </summary>
        public virtual void Pause()
        {
            scheduler.PauseAll();
        }

        /// <summary>
        /// Resumes all activity in server.
        /// </summary>
        public void Resume()
        {
            scheduler.ResumeAll();
        }

        /// <summary>
        /// TopShelf‘s method delegated to <see cref="Start()"/>.
        /// </summary>
        public bool Start(HostControl hostControl)
        {
            Start();
            return true;
        }

        /// <summary>
        /// TopShelf‘s method delegated to <see cref="Stop()"/>.
        /// </summary>
        public bool Stop(HostControl hostControl)
        {
            Stop();
            return true;
        }

        /// <summary>
        /// TopShelf‘s method delegated to <see cref="Pause()"/>.
        /// </summary>
        public bool Pause(HostControl hostControl)
        {
            Pause();
            return true;
        }

        /// <summary>
        /// TopShelf‘s method delegated to <see cref="Resume()"/>.
        /// </summary>
        public bool Continue(HostControl hostControl)
        {
            Resume();
            return true;
        }
    }
}
代码:QuartzServer.cs
技术图片
/// <summary>
    /// Factory class to create Quartz server implementations from.
    /// </summary>
    public class QuartzServerFactory
    {
       // private static readonly ILog logger = LogManager.GetLogger(typeof (QuartzServerFactory));

        /// <summary>
        /// Creates a new instance of an Quartz.NET server core.
        /// </summary>
        /// <returns></returns>
        public static QuartzServer CreateServer()
        {
            string typeName = Configuration.ServerImplementationType;

            Type t = Type.GetType(typeName, true);

          // logger.Debug("Creating new instance of server type ‘" + typeName + "‘");
            QuartzServer retValue = (QuartzServer) Activator.CreateInstance(t);
          //  logger.Debug("Instance successfully created");
            return retValue;
        }
    }
}
代码:QuartzServerFactory.cs
技术图片
# You can configure your scheduler in either <quartz> configuration section
# or in quartz properties file
# Configuration section has precedence

quartz.scheduler.instanceName = ServerScheduler

# configure thread pool info
quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
quartz.threadPool.threadCount = 60

# job initialization plugin handles our xml reading, without it defaults are used
quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins
quartz.plugin.xml.fileNames = ~/quartz_jobs.xml

# export this server to remoting context
quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
quartz.scheduler.exporter.port = 21004
quartz.scheduler.exporter.bindName = QuartzScheduler
quartz.scheduler.exporter.channelType = tcp
quartz.scheduler.exporter.channelName = httpQuartz
配置文件:quartz.config
技术图片
<?xml version="1.0" encoding="UTF-8"?>

<!-- This file contains job definitions in schema version 2.0 format -->

<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>
    <!--ProductTaskJob-->
    <job>
      <name>ProductTaskJob</name>
      <group>ProductTaskJobGroup</group>
      <description>ProductTaskJob for Quartz Server</description>
      <job-type>QuartzNetWindowsService.Order.ProductTaskJob, QuartzNetWindowsService</job-type>
      <durable>true</durable>
      <recover>false</recover>
    </job>
    <trigger>
      <cron>
        <name>ProductTaskJobTrigger</name>
        <group>ProductTaskJobSimpleGroup</group>
        <description>ProductTaskJob trigger List</description>
        <job-name>ProductTaskJob</job-name>
        <job-group>ProductTaskJobGroup</job-group>
        <cron-expression>0/5 * * * * ?</cron-expression>
      </cron>
    </trigger>
    <!--ListJob-->
    <job>
      <name>ListJob</name>
      <group>ListJobGroup</group>
      <description>ListJob for Quartz Server</description>
      <job-type>QuartzNetWindowsService.Order.ListJob, QuartzNetWindowsService</job-type>
      <durable>true</durable>
      <recover>false</recover>
    </job>
    <trigger>
      <cron>
        <name>ListJobTrigger</name>
        <group>ListJobSimpleGroup</group>
        <description>ListJob trigger List</description>
        <job-name>ListJob</job-name>
        <job-group>ListJobGroup</job-group>
        <cron-expression>0/5 * * * * ?</cron-expression>
      </cron>
    </trigger>


    <job>
      <name>sampleJob</name>
      <group>sampleGroup</group>
      <description>Sample job for Quartz Server</description>
      <job-type>QuartzNetWindowsService.SampleJob, QuartzNetWindowsService</job-type>
      <durable>true</durable>
      <recover>false</recover>
      <job-data-map>
        <entry>
          <key>key1</key>
          <value>value1</value>
        </entry>
        <entry>
          <key>key2</key>
          <value>value2</value>
        </entry>
      </job-data-map>
    </job>

    <trigger>
      <simple>
        <name>sampleSimpleTrigger</name>
        <group>sampleSimpleGroup</group>
        <description>Simple trigger to simply fire sample job</description>
        <job-name>sampleJob</job-name>
        <job-group>sampleGroup</job-group>
        <misfire-instruction>SmartPolicy</misfire-instruction>
        <repeat-count>-1</repeat-count>
        <repeat-interval>10000</repeat-interval>
      </simple>
    </trigger>
    <trigger>
      <cron>
        <name>sampleCronTrigger</name>
        <group>sampleCronGroup</group>
        <description>Cron trigger to simply fire sample job</description>
        <job-name>sampleJob</job-name>
        <job-group>sampleGroup</job-group>
        <misfire-instruction>SmartPolicy</misfire-instruction>
        <cron-expression>0/10 * * * * ?</cron-expression>
      </cron>
    </trigger>
    <trigger>
      <calendar-interval>
        <name>sampleCalendarIntervalTrigger</name>
        <group>sampleCalendarIntervalGroup</group>
        <description>Calendar interval trigger to simply fire sample job</description>
        <job-name>sampleJob</job-name>
        <job-group>sampleGroup</job-group>
        <misfire-instruction>SmartPolicy</misfire-instruction>
        <repeat-interval>15</repeat-interval>
        <repeat-interval-unit>Second</repeat-interval-unit>
      </calendar-interval>
    </trigger>
  </schedule>
</job-scheduling-data>
job配置文件:quartz_jobs.xml

 

技术图片
    /// <summary>
    /// A sample job that just prints info on console for demostration purposes.
    /// </summary>
    public class SampleJob : IJob
    {
        /// <summary>
        /// Called by the <see cref="IScheduler" /> when a <see cref="ITrigger" />
        /// fires that is associated with the <see cref="IJob" />.
        /// </summary>
        /// <remarks>
        /// The implementation may wish to set a  result object on the 
        /// JobExecutionContext before this method exits.  The result itself
        /// is meaningless to Quartz, but may be informative to 
        /// <see cref="IJobListener" />s or 
        /// <see cref="ITriggerListener" />s that are watching the job‘s 
        /// execution.
        /// </remarks>
        /// <param name="context">The execution context.</param>
        public async Task Execute(IJobExecutionContext context)
        {
            LoggerProvider.LogInfomation(nameof(SampleJob) + "正在执行中。。。。。。。。。。。。");
            await Task.Delay(1);
        }

    }
}
代码:SampleJob.cs
技术图片
 class Constants
    {
        public const string logPath = "serilog:write-to:File.path";
        public const string logLimitFileSize = "serilog:write-to:File.fileSizeLimitBytes";
    }
    /// <summary>
    /// Serilog日志
    /// </summary>
    public class LoggerProvider
    {
        private readonly static ILogger log;

        static LoggerProvider()
        {
            log = new LoggerConfiguration()
               .WriteTo.File(
                   path: ConfigurationManager.AppSettings[Constants.logPath],
                   rollingInterval: RollingInterval.Day,
                   fileSizeLimitBytes: Convert.ToInt32(ConfigurationManager.AppSettings[Constants.logLimitFileSize]),
                   rollOnFileSizeLimit: true

               ).CreateLogger();
            Console.WriteLine(1122);
        }

        public static void LogInfomation(string message, Exception exception = null)
        {
            log.Information(exception, message);
        }

        public static void LogDebug(string message, Exception exception = null)
        {
            log.Debug(exception, message);
        }

        public static void LogWarning(string message, Exception exception = null)
        {
            log.Warning(exception, message);
        }

        public static void LogError(string message, Exception exception = null)
        {
            log.Error(exception, message);
        }

       
    }
   
}
日志相关文件:LoggerProvider.cs
技术图片
这里设置日志输出的路径和设置日志的文件的大小

<?xml version="1.0" encoding="utf-8" ?>
  <appSettings>
    <add key="serilog:write-to:File.path" value="Logs	est-.txt" />
    <add key="serilog:write-to:File.fileSizeLimitBytes" value="1234567" />
  </appSettings>
配置文件:Test.Production.config

 

 五、修改控制台程序入口

技术图片

 

 

 技术图片

 

 

 

  static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        static void Main()
        {
            Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);

            HostFactory.Run(x =>
            {
                x.RunAsLocalSystem();

                x.SetDescription(Configuration.ServiceDescription);
                x.SetDisplayName(Configuration.ServiceDisplayName);
                x.SetServiceName(Configuration.ServiceName);

                x.Service(factory =>
                {
                    QuartzServer server = QuartzServerFactory.CreateServer();
                    server.Initialize().GetAwaiter().GetResult();
                    return server;
                });
            });
        }
    }
}

 

六、修改配置文件为始终复制

技术图片

 

 

 技术图片

 

 

 

技术图片

 

 

 

七、app.config文件修改,注意标记红色代码

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</configSections>

    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
  <appSettings configSource="Test.Production.config"></appSettings>
</configuration>

 

八、新建一个类库,创建一个产品业务相关类:ProductAppService

然后 QuartzNetWindowsService 引用 类库 ProductAppService,并且新建一个ProductTaskJob:IJob ,实现IJob接口

    public class ProductAppService
    {
        public string GetProductList()
        {
            return nameof(ProductAppService) + ":我获取到产品列表啦!";
        }
    }


   public class ProductTaskJob : IJob

    {
        public async Task Execute(IJobExecutionContext context)
        {
            //具体业务实现
            var result = new BMYAppService.ProductAppService().GetProductList();
            //输出日志
            LoggerProvider.LogInfomation(result);


            await Task.Delay(1);
        }
    }
}

 

 九、把控制台安装到电脑中变成服务

  新建一个文本文件,命名为:安装服务.bat   ,注意:另存为时候,编码格式选择为:ANSI ,保存后再修改后缀名为*.bat,卸载服务也是一样的,这里路径可以自己设置,我为了方便直接复制程序运行路径

技术图片

 

 

技术图片

 

 看下我的安装结果,并查看相关日志记录,证明已经在运行了!

 

 

技术图片

 

 

技术图片

 

 

技术图片

 

 

 技术图片

 

 

至此,整个例子实现完成。如有疑问或错误请留言指正。谢谢!

quart.net 参考文献:https://www.quartz-scheduler.net/

job文件相关表达式执行配置参考:http://qqe2.com/cron

Demo下载:csdn还在审核。

 

以上是关于quartz3.0.7和topshelf4.2.1实现任务调度的主要内容,如果未能解决你的问题,请参考以下文章

第三十一节:扫盲并发和并行同步和异步进程和线程阻塞和非阻塞响应和吞吐等

shell中$()和 ` `${}${!}${#}$[] 和$(()),[ ] 和(( ))和 [[ ]]

Java基础8---面向对象代码块和继承和this和super和重写和重载和final

Java基础8---面向对象代码块和继承和this和super和重写和重载和final

JS中some()和every()和join()和concat()和pop(),push(),shift(),unshfit()和map()和filter()

malloc和free,brk和sbrk和mmap和munmap的使用和关系以及内存分配的原理