欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.net core+topshelf+quartz创建windows定时任务服务

程序员文章站 2022-04-09 15:47:22
.net core+topshelf+quartz创建windows定时任务服务 准备工作 创建.net core 控制台应用程序,这里不做过多介绍 添加TopShelf包:TopShelf; 添加Quartz包:Quartz、Quartz.Plugins; 添加依赖注入包:Microsoft.Ex ......

.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

详情参见

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之前执行了依赖注入等操作,导致服务启动失败,故写下这篇文章

此文章有部分借鉴其他博主的博客,如有侵权,请联系删除