使用WorkService创建定时任务


.NET 中的 Worker Service 入门介绍 - 技术译民 - 博客园 (cnblogs.com)

新建 Worker Services

新建 Worker Services

添加Nlog

手动或使用NuGet在csproj中添加依赖项

  • 安装最新版本:
  • NLog.Web.AspNetCore 4.9+
  • 如有可能,更新NLog软件包

创建nlog.config文件。

在项目的根目录中创建nlog.config(全部小写)文件。
我们使用以下示例:

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


  
  
    
  

  
  
    
    
    
    
    
    
    
  

  
  
    
    
    
    
    
  

注意:日志生成的文件在bin目录下

配置文件的更多详细信息在这里

请注意,如果删除所有其他LoggingProviders(如控制台)并且仅使用NLog,则可能必须特别注意Hosting Lifetime Startup Messages。因为这可能导致托管环境(Visual Studio / Docker / Azure容器)看不到启动的应用程序。

启用复制到bin文件夹

  • .csproj手动编辑文件并添加:
    
        
    
    

修改 program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Web;

namespace DemoWorkerService
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
            try
            {
                logger.Debug("init main");
                CreateHostBuilder(args).Build().Run();
            }
            catch (Exception exception)
            {
                //NLog: catch setup errors
                logger.Error(exception, "Stopped program because of exception");
                throw;
            }
            finally
            {
                // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
                NLog.LogManager.Shutdown();
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService();
                }).ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                })
.UseNLog();  // NLog: Setup NLog for Dependency injection;
    }
}

配置 appsettings.json

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Trace",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

写日志

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace DemoWorkerService
{
    public class Worker : BackgroundService
    {
        private readonly ILogger _logger;

        public Worker(ILogger logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
}

运行后日志不在控制台显示

注意:日志生成的文件在bin目录下

使用Quartz.Net

安装Quartz.Net

手动或使用NuGet在csproj中添加依赖项

  • 安装最新版本:

    Microsoft.Extensions.Hosting 5.0+

    Quartz.Extensions.Hosting 3.3.3+

  • 如有可能,更新Quartz软件包

使用Quartz.Net

  • 创建ServiceCollectionQuartzConfiguratorExtensions.cs拓展文件

    using Microsoft.Extensions.Configuration;
    using Quartz;
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace DemoWorkerService
    {
        public static class ServiceCollectionQuartzConfiguratorExtensions
        {
            public static void AddJobAndTrigger(
            this IServiceCollectionQuartzConfigurator quartz,
            IConfiguration config)
            where T : IJob
            {
                // Use the name of the IJob as the appsettings.json key
                string jobName = typeof(T).Name;
    
                // Try and load the schedule from configuration
                var configKey = $"Quartz:{jobName}";
                var cronSchedule = config[configKey];
    
                // Some minor validation
                if (string.IsNullOrEmpty(cronSchedule))
                {
                    throw new Exception($"No Quartz.NET Cron schedule found for job in configuration at {configKey}");
                }
    
                // register the job as before
                var jobKey = new JobKey(jobName);
                quartz.AddJob(opts => opts.WithIdentity(jobKey));
    
                quartz.AddTrigger(opts => opts
                    .ForJob(jobKey)
                    .WithIdentity(jobName + "-trigger")
                    .WithCronSchedule(cronSchedule)); // use the schedule from configuration
            }
        }
    }
    
    
  • 创建HelloWorldJob.cs任务文件

    using Microsoft.Extensions.Logging;
    using Quartz;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace DemoWorkerService.Jobs
    {
        [DisallowConcurrentExecution]
        public class HelloWorldJob : IJob
        {
            private readonly ILogger _logger;
            public HelloWorldJob(ILogger logger)
            {
                _logger = logger;
            }
    
            public Task Execute(IJobExecutionContext context)
            {
                _logger.LogInformation("Hello world!");
                return Task.CompletedTask;
            }
        }
    }
    
    
  • 添加服务

     public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .UseWindowsService()
                    .ConfigureServices((hostContext, services) =>
                    {
                        // Add the required Quartz.NET services
                        services.AddQuartz(q =>
                        {
                            q.UseMicrosoftDependencyInjectionJobFactory();
    		
    						// Register the job, loading the schedule from configuration
                            q.AddJobAndTrigger(hostContext.Configuration);
                        });
    
                        // WaitForJobsToComplete:此设置确保当请求关闭时,Quartz.NET在退出之前等待作业优雅地结束。
                        services.AddQuartzHostedService(
                            q => q.WaitForJobsToComplete = true);
    
                        // other config
                    }).ConfigureLogging(logging =>
                    {
                        logging.ClearProviders();
                        logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                    })
                    .UseNLog();  // NLog: Setup NLog for Dependency injection;
    

    文件将不会打印再控制器台中

  • 添加配置文件再appsettings.json

    "Quartz": {
    	"HelloWorldJob": "0/5 * * * * ?"
    }
    

添加 Windows Services 依赖

为了作为 Windows 服务运行,我们需要我们的 Worker 监听来自 ServiceBase 的启动和停止信号,ServiceBase 是将 Windows 服务系统公开给 .NET 应用程序的 .NET 类型。为此,我们需要添加 Microsoft.Extensions.Hosting.WindowsServices NuGet 包:

dotnet add package Microsoft.Extensions.Hosting.WindowsServices

然后修改 Program.cs 中的 CreateHostBuilder 方法,添加 UseWindowsService 方法调用:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseWindowsService()
                .ConfigureServices((hostContext, services) =>
                {
                    
                }).ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                })
                .UseNLog();  // NLog: Setup NLog for Dependency injection;

发布程序

dotnet publish -c Release -r win-x64 -o D:\DemoWorkerService

D:\DemoWorkerService为发布后的地址

创建并运行任务

当我们使用 sc.exe 实用工具管理服务时,必须以管理员身份运行 Windows 命令提示符,否则会执行失败。

  • 安装服务

    sc create 我的服务名称 binPath= "D:\DemoWorkerService\DemoWorkerService.exe" start= auto displayname= "服务描述"
    

    1、每个命令行选项 (参数) 必须包含等号作为选项名称的一部分。
    2、选项与其值之间必须有一个空格(例如,type= own),如果遗漏了空格,操作将失败。

    sc create
    
    描述:
            在注册表和服务数据库中创建服务项。
    用法:
            sc  create [service name] [binPath= ]  ...
    
    选项:
    注意: 选项名称包括等号。
          等号和值之间需要一个空格。
     type= 
           (默认 = own)
     start= 
           (默认 = demand)
     error= 
           (默认 = normal)
     binPath= <.exe 文件的 BinaryPathName>
     group= 
     tag= 
     depend= <依存关系(以 / (斜杠)分隔)>
     obj= 
           (默认= LocalSystem)
     DisplayName= <显示名称>
     password= <密码>
    
  • 设置服务描述

    sc description
    描述:
            设置服务的描述字符串。
    用法:
            sc  description [service name] [description]
    
    sc description 我的服务名称 "服务描述"
    
  • 启动服务

    sc start 我的服务名称
    
  • 停止服务

    sc stop 我的服务名称
    
  • 删除服务

    sc delete 我的服务名称