如何在 ASP.NET 核心应用程序中使用 BackgroundService 每年执行一个方法?

How can I execute a method every year using BackgroundService in ASP.NET core Application?

     public async Task DoWork(CancellationToken cancellationToken)
         { 
             var scope = serviceScopeFactory.CreateScope();
             var context = scope.ServiceProvider.GetService<ApplicationDbContext>();
             while (!cancellationToken.IsCancellationRequested)
                {
                  foreach(var x in context.Students.ToList())
                  {
                   x.Class ++;
                   context.Students.Update(x);
                  } 
                     context.SaveChanges();
                     await Task.Delay(1000);
                 }
        }

我想每年增加 class 学生 1。我想每个新年自动执行这个方法,

这是一个架构问题,而不是编码问题。有几种方法可以实现这一点。 Hangfire是其中最简单的:

1.Hangfire

RecurringJob.AddOrUpdate(() =>{
  foreach(var x in context.Students.ToList()) {
    x.Class++;
    context.Students.Update(x);
  }
  context.SaveChanges();
},
Cron.Yearly);

检查他们的文档以维护(edit/delete/add)个重复性工作

2.Ubuntu 计划任务

添加 .net 核心控制台应用程序并将其设置为 ubuntu 服务器的 cron 作业 https://ermir.net/topic-2/execute-net-core-console-application-as-an-ubuntu-cron-job

3.Windows 服务任务计划程序

如果您使用 windows 服务器作为主机,您可以看到:https://medium.com/better-programming/asp-net-core-windows-service-task-scheduler-daily-weekly-monthly-700a569d502a

或者将您的 .net 核心制作成一个 exe 文件并按照此 https://superuser.com/questions/239427/setting-an-annual-task-on-windows-task-scheduler?rq=1

  1. 云托管功能

您可以使用 Azure 函数或 Amazon lambda 等云函数来 运行 recurring/scheduled jobs/tasks https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-scheduled-function https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents-tutorial.html

请参考我在本帖的回复:

如果你想实现定时任务,建议你也可以尝试使用Cronos package and the Cron Expressions to configure the scheduled task (reference: link).

Cronos 包是一个轻量级但功能完备的库,用于解析 cron 表达式并计算下一次出现时区和夏令时。 Cronos 是 HangfireIO 赞助的开源项目,详细文档可以阅读 its GitHub repository。 详细步骤如下:

  1. 通过 NuGet 安装 Cronos 包。

  2. 使用以下代码创建一个 CronJobService 服务:

     public abstract class CronJobService : IHostedService, IDisposable
     {
         private System.Timers.Timer _timer;
         private readonly CronExpression _expression;
         private readonly TimeZoneInfo _timeZoneInfo;
    
         protected CronJobService(string cronExpression, TimeZoneInfo timeZoneInfo)
         {
             _expression = CronExpression.Parse(cronExpression);
             _timeZoneInfo = timeZoneInfo;
         }
    
         public virtual async Task StartAsync(CancellationToken cancellationToken)
         {
             await ScheduleJob(cancellationToken);
         }
    
         protected virtual async Task ScheduleJob(CancellationToken cancellationToken)
         {
             var next = _expression.GetNextOccurrence(DateTimeOffset.Now, _timeZoneInfo);
             if (next.HasValue)
             {
                 var delay = next.Value - DateTimeOffset.Now;
                 if (delay.TotalMilliseconds <= 0)   // prevent non-positive values from being passed into Timer
                 {
                     await ScheduleJob(cancellationToken);
                 }
                 _timer = new System.Timers.Timer(delay.TotalMilliseconds);
                 _timer.Elapsed += async (sender, args) =>
                 {
                     _timer.Dispose();  // reset and dispose timer
                     _timer = null;
    
                     if (!cancellationToken.IsCancellationRequested)
                     {
                         await DoWork(cancellationToken);
                     }
    
                     if (!cancellationToken.IsCancellationRequested)
                     {
                         await ScheduleJob(cancellationToken);    // reschedule next
                     }
                 };
                 _timer.Start();
             }
             await Task.CompletedTask;
         }
    
         public virtual async Task DoWork(CancellationToken cancellationToken)
         {
             await Task.Delay(5000, cancellationToken);  // do the work
         }
    
         public virtual async Task StopAsync(CancellationToken cancellationToken)
         {
             _timer?.Stop();
             await Task.CompletedTask;
         }
    
         public virtual void Dispose()
         {
             _timer?.Dispose();
         }
     }
    
     public interface IScheduleConfig<T>
     {
         string CronExpression { get; set; }
         TimeZoneInfo TimeZoneInfo { get; set; }
     }
    
     public class ScheduleConfig<T> : IScheduleConfig<T>
     {
         public string CronExpression { get; set; }
         public TimeZoneInfo TimeZoneInfo { get; set; }
     }
    
     public static class ScheduledServiceExtensions
     {
         public static IServiceCollection AddCronJob<T>(this IServiceCollection services, Action<IScheduleConfig<T>> options) where T : CronJobService
         {
             if (options == null)
             {
                 throw new ArgumentNullException(nameof(options), @"Please provide Schedule Configurations.");
             }
             var config = new ScheduleConfig<T>();
             options.Invoke(config);
             if (string.IsNullOrWhiteSpace(config.CronExpression))
             {
                 throw new ArgumentNullException(nameof(ScheduleConfig<T>.CronExpression), @"Empty Cron Expression is not allowed.");
             }
    
             services.AddSingleton<IScheduleConfig<T>>(config);
             services.AddHostedService<T>();
             return services;
         }
     }
    
  3. 创建 ScheduleJob.cs:

     public class ScheduleJob: CronJobService
     {
         private readonly ILogger<ScheduleJob> _logger;
    
         public ScheduleJob(IScheduleConfig<ScheduleJob> config, ILogger<ScheduleJob> logger)
             : base(config.CronExpression, config.TimeZoneInfo)
         {
             _logger = logger;
         }
    
         public override Task StartAsync(CancellationToken cancellationToken)
         {
             _logger.LogInformation("ScheduleJob starts.");
             return base.StartAsync(cancellationToken);
         }
    
         public override Task DoWork(CancellationToken cancellationToken)
         {
             _logger.LogInformation($"{DateTime.Now:hh:mm:ss} ScheduleJob is working.");
             return Task.CompletedTask;
         }
    
         public override Task StopAsync(CancellationToken cancellationToken)
         {
             _logger.LogInformation("ScheduleJob is stopping.");
             return base.StopAsync(cancellationToken);
         }
     }
    
  4. 在 ConfigureServices 方法中注册 ScheduleJob 服务。

     public void ConfigureServices(IServiceCollection services)
     {
         services.AddHostedService<HelloWorldHostedService>(); 
    
         services.AddCronJob<ScheduleJob>(c=>
         {
             c.TimeZoneInfo = TimeZoneInfo.Local;
             c.CronExpression = @"25 21 * * *"; // 21:25 PM daily.
         });
    
         services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
     }
    

    则结果如下:

在上面的示例中,它会在每天 21:25 下午 运行 安排作业。您可以将 CronExpression 更改为 0 0 1 1 *,那么该作业将在每年 1 月 1 日午夜 运行 执行一次。