zoukankan      html  css  js  c++  java
  • 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/

    quartz.net github:https://github.com/quartznet/quartznet/releases

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

    Demo下载:https://download.csdn.net/download/u011195704/11982812

  • 相关阅读:
    串口通信(2)
    串口通信(1)
    extern关键字的使用
    volatile关键字的使用
    Uart串口与RS232串口的区别
    DSP5509的时钟发生器(翻译总结自TI官方文档)
    DSP中的cmd文件
    pragma伪指令
    在C语言中嵌入汇编语言
    another app is currently holding the yum lock;waiting for it to exit...
  • 原文地址:https://www.cnblogs.com/bmyblogs/p/11878892.html
Copyright © 2011-2022 走看看