如何在 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
- 云托管功能
您可以使用 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。
详细步骤如下:
通过 NuGet 安装 Cronos 包。
使用以下代码创建一个 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;
}
}
创建 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);
}
}
在 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 日午夜 运行 执行一次。
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
- 云托管功能
您可以使用 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。 详细步骤如下:
通过 NuGet 安装 Cronos 包。
使用以下代码创建一个 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; } }
创建 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); } }
在 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 日午夜 运行 执行一次。