• 使用WorkService創建定時任務

    使用WorkService創建定時任務

    青杉 877 2021-11-23

    .NET 中的 Worker Service 入門介紹 - 技術譯民 - 博客園 (cnblogs.com)

    在 ASP.NET Core和Worker Service中使用Quartz.Net - 碼農譯站 - 博客園 (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" ?>
    <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    
      <!-- enable asp.net core layout renderers -->
      <extensions>
        <add assembly="NLog.Web.AspNetCore"/>
      </extensions>
    
      <!-- the targets to write to -->
      <targets>
        <!--寫入文件-->
        <target
         xsi:type="File"
         name="DebugFile"
         fileName="Logs\Debug\${shortdate}.log"
         layout="日志時間:${longdate}${newline}日志來源:${callsite}${newline}日志級別:${uppercase:${level}}${newline}消息內容:${message}${newline}----------------------------------------------------------------${newline}" >
        </target>
        <target
          xsi:type="File"
          name="InfoFile"
          fileName="Logs\Info\${shortdate}.log"
          layout="日志時間:${longdate}${newline}日志來源:${callsite}${newline}日志級別:${uppercase:${level}}${newline}消息內容:${message}${newline}----------------------------------------------------------------${newline}" >
        </target>
        <target
          xsi:type="File"
          name="ErrorFile"
          fileName="Logs\Error\${shortdate}.log"
          layout="日志時間:${longdate}${newline}日志來源:${callsite}${newline}日志級別:${uppercase:${level}}${newline}消息內容:${message}${newline}異常信息:${exception:format=tostring}${newline}----------------------------------------------------------------${newline}" >
        </target>
      </targets>
    
      <!-- rules to map from logger name to target -->
      <rules>
        <!--Skip non-critical Microsoft logs and so log only own logs-->
        <logger name="Microsoft.*" maxlevel="Info" final="true" />
        <logger name="*" minlevel="Debug" maxLevel="Debug" writeTo="DebugFile" />
        <logger name="*" minlevel="Info" maxLevel="Info" writeTo="InfoFile" />
        <logger name="*" minlevel="Error" maxLevel="Error" writeTo="ErrorFile" />
      </rules>
    </nlog>
    

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

    配置文件的更多詳細信息在這里

    請注意,如果刪除所有其他LoggingProviders(如控制臺)并且僅使用NLog,則可能必須特別注意Hosting Lifetime Startup Messages。因為這可能導致托管環境(Visual Studio / Docker / Azure容器)看不到啟動的應用程序。

    啟用復制到bin文件夾

    • .csproj手動編輯文件并添加:
      <ItemGroup>
          <Content Update="nlog.config" CopyToOutputDirectory="PreserveNewest" />
      </ItemGroup>
      

    修改 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<Worker>();
                    }).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<Worker> _logger;
    
            public Worker(ILogger<Worker> 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<T>(
              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<T>(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<HelloWorldJob> _logger;
              public HelloWorldJob(ILogger<HelloWorldJob> 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<HelloWorldJob>(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 <server> create [service name] [binPath= ] <option1> <option2>...
      
      選項:
      注意: 選項名稱包括等號。
            等號和值之間需要一個空格。
       type= <own|share|interact|kernel|filesys|rec|userown|usershare>
             (默認 = own)
       start= <boot|system|auto|demand|disabled|delayed-auto>
             (默認 = demand)
       error= <normal|severe|critical|ignore>
             (默認 = normal)
       binPath= <.exe 文件的 BinaryPathName>
       group= <LoadOrderGroup>
       tag= <yes|no>
       depend= <依存關系(以 / (斜杠)分隔)>
       obj= <AccountName|ObjectName>
             (默認= LocalSystem)
       DisplayName= <顯示名稱>
       password= <密碼>
      
    • 設置服務描述

      sc description
      描述:
              設置服務的描述字符串。
      用法:
              sc <server> description [service name] [description]
      
      sc description 我的服務名稱 "服務描述"
      
    • 啟動服務

      sc start 我的服務名稱
      
    • 停止服務

      sc stop 我的服務名稱
      
    • 刪除服務

      sc delete 我的服務名稱
      

    # 定時任務 # Quartz


    国产一级A级高清毛片