震惊!Windows Service服务和定时任务框架quartz之间原来是这种关系……

折月煮酒 提交于 2020-08-09 14:22:55
过场CG:
 
接到公司领导的文件指示,“小熊”需要在6月底去海外执行一个行动代号为【定时任务】的营救计划,这个计划关系到公司某个项目的生死(数据安全漏洞),作战部拟定两个作战方案:
  方案一:使用务定时任务框架quartz;
  方案二:使用windows Service服务。
 
最终的作战方案为:两者配套使用。

 
前言:项目开发完成后,对接的项目有很多个模块,由于其中的一个环节疏忽,现在需要在原有的基础上把缺失的数据自动写入数据库存储起来。
重新修改程序逻辑已然不现实,现在需要一个补丁来进行逻辑更正。
补丁逻辑:两个入口控制,
  • 入口一:点击【更新】按钮同步逻辑后的数据;
  • 入口二:每天晚上18:00进行执行同步逻辑后的数据;

 
现在我们先使用window服务进行入口二的编写(入口一只需要一个按钮调用入口二的逻辑即可)
windows服务

一、开发环境

操作系统:Windows 7 X64/32

开发环境:VS2017

编程语言:C#

.NET版本:.NET Framework 4.6.1

二、创建Windows Service

1、新建一个Windows Service,并将项目名称改为“MyWindowsService”,如下图所示:

 

 2、在解决方案资源管理器内将Service1.cs改为MyWindowsService.cs后并在左边页面空白处点击鼠标右键,添加安装程序,如下图所示:

 

添加安装程序:

 

 

3、 此时软件会生成两个组件,分别为“serviceInstaller1”及“serviceProcessInstaller1”,点击“serviceInstaller1”,右键--->属性,

将ServiceName改为MyWindowsService,Description改为“我的服务”,如下图:

 

 

 4、点击“serviceProcessInstaller1”,在“属性”窗体将Account改为LocalSystem(服务属性系统级别),如下图所示:

 
 
 5、点击MyWindowsService.cs,在左边空白位置右键,“查看代码”,然后编写代码,代码我编写好了,直接拷贝即可使用
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MyWindowsService
{
    public partial class MyWindowsService : ServiceBase
    {
        public MyWindowsService()
        {
            InitializeComponent();
        }

        //创建进程
        public static Thread threadStartConfirmActualTime;  //创建一个时间进程
        public static Thread threadDoCheck;                 //检查日志时间进程

        //开启服务
        protected override void OnStart(string[] args)
        {
            Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\t服务启动!\n");
            StartServer();
        }

        //停止服务
        protected override void OnStop()
        {
            Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\t服务停止!\n");
        }

        //启动服务操作
        private void StartServer()
        {
            try
            {
                threadStartConfirmActualTime = new Thread(new ThreadStart(new SingleClass().BeginConfirmMessageTime));//在进程下面创建线程
                threadStartConfirmActualTime.Start();
                threadDoCheck.Start();
            }
            catch (Exception ex)
            {
                threadStartConfirmActualTime.Abort();//关闭线程
                Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\t服务停止!"+ex.Message+"\n");
            }
        }


        /// <summary>
        /// Aouth:xiongze
        /// Time:2020/06/02
        /// Details:单例模式_建立一个单例类,保证只有一个对象被实例化,然后开启服务
        /// </summary>
        public class SingleClass  //单例模式_建立一个单例类,保证只有一个对象被实例化
        {
            public static SingleClass _SingleClass;
            public static object onlock = new object();  //实例化一个锁

            public static SingleClass Singleton
            {
                get
                {
                    if (_SingleClass == null)
                    {
                        lock (onlock)
                        {
                            _SingleClass = new SingleClass();
                        }
                    }
                    return _SingleClass;
                }
            }
            public void BeginConfirmMessageTime()  //开启服务
            {
                while (true)
                {
                    //每天晚上18这一个小时内检测执行
                    if (DateTime.Now.Hour.ToString("18") == "18")
                    {
                        try
                        {
                            //同步数据
                            Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "我在"+ DateTime.Now + "同步了数据哦!\n");
                        }
                        catch (Exception ex)
                        {
                            //记录错误日志(记录到相应的文件下面)
                            Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\t我是错误日志!" + ex.Message + "\n");
                        }
                    }
                    Thread.Sleep(1800000);  //半个小时执行一次,注意,1000毫秒=1秒,具体需要多少时间可以自由换算 1800000半小时
                }

            }
        }

    }
}
 
6、至此,Windows服务已经创建完毕。
 
 三、创建安装、启动、停止、卸载服务的Windows窗体
 
1、点击项目,右键,重新生成
在桌面上创建一个文件夹,命名为“我的服务”,将MyWindowsService项目项目生成的bin文件夹Debug文件夹的内容全部复制到新建的文件夹里面;
然后去百度拷贝三个文件到“我的服务”文件里面,分别为Install.bat(安装)、UnInstall.bat(卸载)、InstallUtil.exe(执行),
打开文件,分别打开Install.bat和UnInstall.bat文件,将后面一个xxx.exe修改为你的文件程序,我们的是MyWindowsService.exe。如下图

 

 

 

 

操作完后双击Install.bat进行安装windows服务,安装成功后点击【计算机】-->右键-->管理-->服务里面找到“我的服务”,启动服务并修改为自动启动;如下图:
 

 

 

 

 

 这样就实现了windows服务入口二每天晚上18:00进行执行同步逻辑后的数据,只要代码不报错就一直执行;
 
优点:每天指定时间自动执行指定逻辑
缺点:程序在每次设置的时间内无限执行,消耗资源(CPU等)
 
 
 quartz定时任务

 

一、开发环境

操作系统:Windows 7 X64

开发环境:VS2017

编程语言:C#

.NET版本:.NET Framework 4.6.1

二、创建quartz定时任务
1、创建一个控制台任务程序进行演示,命名为MyQuartz,创建如下:
 

 

 2、引入quartz框架动态链接库

在NuGet管理里面搜索quartz进行安装,注意: Quartz高版本的存在兼容性,建议使用低版本的(2.5.0)
如下图:
 

 

 

3、创建一个执行的类,用于执行后台数据逻辑,命名为TestJob,并且继承Quartz框架的IJob接口,这个累的内容如下,可以直接拷贝
using Quartz;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyQuartz
{
    public class TestJob: IJob
    {
        public void Execute(IJobExecutionContext context)//指定调用的方法
        {
            try
            {
                //在这里写代码(写自己的业务逻辑)
                Console.WriteLine("任务执行啦" + DateTime.Now);
            }
            catch (Exception ex)
            {
                Console.WriteLine("定时任务出错" + ex.Message);
            }
        }
    }
}

4、在Program.cs文件里面进行调用编写,编写内容主要如下:

  1. 创建一个作业调度池;
  2. 创建出来一个具体的作业;
  3. 创建并配置一个触发器;
  4. 加入作业调度池中;
  5. 开始运行。
 首先我们看完成代码,然后进行讲解(代码可以直接拷贝):
using Quartz;
using Quartz.Impl;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyQuartz
{
    class Program
    {
        static void Main(string[] args)
        {
            //1.首先创建一个作业调度池
            ISchedulerFactory schedf = new StdSchedulerFactory();
            IScheduler sched = schedf.GetScheduler();
            //2.创建出来一个具体的作业
            IJobDetail job = JobBuilder.Create<TestJob>().Build();
            //3.创建并配置一个触发器

            #region(使用SimpleTrigger触发器,每次3秒执行一次,无上限)
            ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create().WithSimpleSchedule(x => x.WithIntervalInSeconds(3).WithRepeatCount(int.MaxValue)).Build();
            #endregion

            #region 每3秒执行一次 总共5次 ,开始执行时间设定在当前时间,结束时间我设定在2小时后,不过5次执行完没2小时候都不再执行。
            //-------NextGivenSecondDate:如果第一个参数为null则表名当前时间往后推迟2秒的时间点。
            //DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(DateTime.Now.AddSeconds(5), 2);
            //DateTimeOffset endTime = DateBuilder.NextGivenSecondDate(DateTime.Now.AddHours(2), 3);
            //ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create().StartAt(startTime).EndAt(endTime)
            //                            .WithSimpleSchedule(x => x.WithIntervalInSeconds(3).WithRepeatCount(5))
            //                            .Build();
            #endregion

            #region (使用CronTrigger触发器)在每小时的第10,20,25,26,33,54分钟,每分钟的第1,10,14秒执行一次
            //DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(DateTime.Now.AddSeconds(1), 2);
            //DateTimeOffset endTime = DateBuilder.NextGivenSecondDate(DateTime.Now.AddYears(2), 3);
            //ICronTrigger trigger = (ICronTrigger)TriggerBuilder.Create().StartAt(startTime).EndAt(endTime)
            //                            .WithCronSchedule("1,10,59 10,20,21,26,33,54 * * * ? ")
            //                            .Build();
            #endregion
            //4.加入作业调度池中
            sched.ScheduleJob(job, trigger);
            //5.开始运行
            sched.Start();
            Console.ReadKey();

        }
    }
}

 

在上面代码中可以看出,我们主要使用了两个触发器:SimpleTrigger触发器和CronTrigger触发器;

SimpleTrigger触发器(简单触发器SimpleTrigger)

SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。

根据描述,你可能已经发现了,SimpleTrigger的属性包括:开始时间、结束时间、重复次数以及重复的间隔。这些属性的含义与你所期望的是一致的,只是关于结束时间有一些地方需要注意。

重复次数,可以是0、正整数,以及常量SimpleTrigger.REPEAT_INDEFINITELY。重复的间隔,必须是0,或者long型的正数,表示毫秒。注意,如果重复间隔为0,trigger将会以重复次数并发执行(或者以scheduler可以处理的近似并发数)。

如果你还不熟悉DateBuilder,了解后你会发现使用它可以非常方便地构造基于开始时间(或终止时间)的调度策略。

endTime属性的值会覆盖设置重复次数的属性值;比如,你可以创建一个trigger,在终止时间之前每隔10秒执行一次,你不需要去计算在开始时间和终止时间之间的重复次数,只需要设置终止时间并将重复次数设置为REPEAT_INDEFINITELY(当然,你也可以将重复次数设置为一个很大的值,并保证该值比trigger在终止时间之前实际触发的次数要大即可)。

 

具体用法我们就不水文了,大家去看Quartz官网文档的用法即可SimpleTrigger触发器使用规则:https://www.w3cschool.cn/quartz_doc/quartz_doc-67a52d1f.html,部分截图显示如下:
  • 指定时间开始触发,不重复:
  • 指定时间触发,每隔10秒执行一次,重复10次:
  • 5分钟以后开始触发,仅执行一次:
  • 立即触发,每个5分钟执行一次,直到22:00:
  • 建立一个触发器,将在下一个小时的整点触发,然后每2小时重复一次:

 

 

CronTriggerr触发器(基于Cron表达式的触发器CronTriggerr

CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。

使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。

即使如此,和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。

Cron Expressions
Cron-Expressions用于配置CronTrigger的实例。Cron Expressions是由七个子表达式组成的字符串,用于描述日程表的各个细节。这些子表达式用空格分隔,并表示:

Seconds
Minutes
Hours
Day-of-Month
Month
Day-of-Week
Year (optional field)
一个完整的Cron-Expressions的例子是字符串“0 0 12?* WED“ - 这意味着”每个星期三下午12:00“。
单个子表达式可以包含范围和/或列表。例如,可以用“MON-FRI”,“MON,WED,FRI”或甚至“MON-WED,SAT”代替前一个(例如“WED”)示例中的星期几字段。
通配符(' '字符)可用于说明该字段的“每个”可能的值。因此,前一个例子的“月”字段中的“”字符仅仅是“每个月”。因此,“星期几”字段中的“*”显然意味着“每周的每一天”。
所有字段都有一组可以指定的有效值。这些值应该是相当明显的 - 例如秒和分钟的数字0到59,数小时的值0到23。日期可以是1-31的任何值,但是您需要注意在给定的月份中有多少天!月份可以指定为0到11之间的值,或者使用字符串JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV和DEC。星期几可以指定为1到7(1 =星期日)之间的值,或者使用字符串SUN,MON,TUE,WED,THU,FRI和SAT。
'/'字符可用于指定值的增量。例如,如果在“分钟”字段中输入“0/15”,则表示“每隔15分钟,从零开始”。如果您在“分钟”字段中使用“3/20”,则意味着“每隔20分钟,从三分钟开始” - 换句话说,它与“分钟”中的“3,243,43”相同领域。请注意“ / 35”的细微之处并不代表“每35分钟” - 这意味着“每隔35分钟,从零开始” - 或者换句话说,与指定“0,35”相同。
'' 字符是允许的日期和星期几字段。用于指定“无特定值”。当您需要在两个字段中的一个字段中指定某个字符而不是另一个字段时,这很有用。请参阅下面的示例(和CronTrigger JavaDoc)以进行说明。
“L”字符允许用于月日和星期几字段。这个角色对于“最后”来说是短暂的,但是在这两个领域的每一个领域都有不同的含义。例如,“月”字段中的“L”表示“月的最后一天” - 1月31日,非闰年2月28日。如果在本周的某一天使用,它只是意味着“7”或“SAT”。但是如果在星期几的领域中再次使用这个值,就意味着“最后一个月的xxx日”,例如“6L”或“FRIL”都意味着“月的最后一个星期五”。您还可以指定从该月最后一天的偏移量,例如“L-3”,这意味着日历月份的第三个到最后一天。当使用'L'选项时,重要的是不要指定列表或值的范围,因为您会得到混乱/意外的结果。
“W”用于指定最近给定日期的工作日(星期一至星期五)。例如,如果要将“15W”指定为月日期字段的值,则意思是:“最近的平日到当月15日”。
''用于指定本月的“第n个”XXX工作日。例如,“星期几”字段中的“63”或“FRI#3”的值表示“本月的第三个星期五”。
以下是一些表达式及其含义的更多示例 - 您可以在JavaDoc中找到更多的org.quartz.CronExpression

Cron Expressions示例
CronTrigger示例1 - 创建一个触发器的表达式,每5分钟就会触发一次
“0 0/5 * * *?”

CronTrigger示例2 - 创建触发器的表达式,每5分钟触发一次,分钟后10秒(即上午10时10分,上午10:05:10等)。
“10 0/5 * * *?”

CronTrigger示例3 - 在每个星期三和星期五的10:3011:3012:30和13:30创建触发器的表达式。
“0 30 10-13?* WED,FRI“

CronTrigger示例4 - 创建触发器的表达式,每个月5日和20日上午8点至10点之间每半小时触发一次。请注意,触发器将不会在上午10点开始,仅在8:008:309:00和9:300 0/30 8-9 5,20 *?”

请注意,一些调度要求太复杂,无法用单一触发表示 - 例如“每上午9:00至10:00之间每5分钟,下午1:00至晚上10点之间每20分钟”一次。在这种情况下的解决方案是简单地创建两个触发器,并注册它们来运行相同的作业。

 

具体使用方法见CronTrigger触发器使用规则https://www.w3cschool.cn/quartz_doc/quartz_doc-lwuv2d2a.html

  • 建立一个触发器,每隔一分钟,每天上午8点至下午5点之间:
  • 建立一个触发器,将在上午10:42每天发射:
  • 建立一个触发器,将在星期三上午10:42在TimeZone(系统默认值)之外触发:

 

 

 


执行演示

写完后我们查看执行结果,我使用的是SimpleTrigger触发器,每3秒执行一次,无上限,各位可以根据自身的项目需求更改使用不同的触发器

 

 

注意:

如果定时任务框架quartz这个挂在iis上会被回收掉(默认是20分钟)

Quartz高版本的存在兼容性,建议使用低版本的(2.5.0)

 


 

总结

到这里Windows Service服务和定时任务框架quartz都简单的介绍完了,具体使用哪一个或者配套使用就看本身项目逻辑了;

现在执行的逻辑:

Windows Service服务:程序随电脑开机启动,每隔半个小时执行一次,检测到执行时间等于我设置的时间就去执行后台逻辑;

定时任务框架quartz:如果发布在iis上,默认20分钟后会被回收(程序不能一直等待执行),程序处于休眠状态,到指定时候后唤醒(触发器)程序执行后台逻辑;

 

PS:如果把quartz结合windows服务使用的话就不存在被回收问题;

 
欢迎关注订阅我的微信公众平台【熊泽有话说】,更多好玩易学知识等你来取
作者:熊泽-学习中的苦与乐
公众号:熊泽有话说
出处: https://www.cnblogs.com/xiongze520/p/13031944.html
创作不易,任何人或团体、机构全部转载或者部分转载、摘录,请在文章明显位置注明作者和原文链接。  



 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!