Mastering Scheduled Tasks in .NET with Cronos
Cronos is a task scheduling library for .NET that allows scheduling and executing tasks at specific times or intervals using the CRON pattern.
In this article, I will present how to set up and use task scheduling in the background in a .NET 8 application.
Prerequisites
- .NET 8.0
- Visual Studio 2022
- Nuget package Cronos
Web API
In the Program.cs
class of the API, configure as shown below:
using Sample.Scheduler.Core.Extensions;
using Sample.Scheduler.Core.TimerSchedulers;
var builder = WebApplication.CreateBuilder(args);
builder.AddSerilog();
builder.Services.AddRouting(options => options.LowercaseUrls = true);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCronJob<TimerSendEmail>(c => c.CronExpression = @"0 */1 * * * *");
builder.Services.AddCronJob<TimerCheckDatabase>(c => c.CronExpression = @"* * * * * *");
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
app.Run();
The next step is to create the extension class CronJobExtensions, responsible for controlling the next scheduling of each task using the CronExpression:
public abstract class CronJobExtensions : BackgroundService
{
private readonly CronExpression _expression;
private readonly TimeZoneInfo _timeZoneInfo;
private readonly IServiceProvider _serviceProvider;
protected CronJobExtensions(string cronExpression, TimeZoneInfo timeZoneInfo, IServiceProvider serviceProvider)
{
_expression = CronExpression.Parse(cronExpression, CronFormat.IncludeSeconds);
_timeZoneInfo = timeZoneInfo;
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var now = DateTimeOffset.Now;
var next = _expression.GetNextOccurrence(now, _timeZoneInfo);
if (!next.HasValue) continue;
var delay = next.Value - now;
await Task.Delay(delay, cancellationToken);
if (cancellationToken.IsCancellationRequested)
continue;
try
{
using var scope = _serviceProvider.CreateScope();
await DoWork(scope, cancellationToken);
}
catch (Exception ex)
{
Log.Error(ex, nameof(ExecuteAsync));
}
}
}
public abstract Task DoWork(IServiceScope scope, CancellationToken cancellationToken);
}
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; } = TimeZoneInfo.Local;
}
Next, it is necessary to create the extension class ScheduledServiceExtensions responsible for registering the schedules:
public static class ScheduledServiceExtensions
{
public static IServiceCollection AddCronJob<T>(this IServiceCollection services, Action<IScheduleConfig<T>> options) where T : CronJobExtensions
{
var config = new ScheduleConfig<T>();
options.Invoke(config);
services.AddSingleton<IScheduleConfig<T>>(config);
services.AddHostedService<T>();
return services;
}
}
And finally, let’s create the class for scheduling the actions, in this example, we will create a schedule to be triggered every second and another to be triggered every minute. These two schedules are registered in the Program.cs
class
public class TimerCheckDatabase : CronJobExtensions
{
public TimerCheckDatabase(IScheduleConfig<TimerCheckDatabase> config, IServiceProvider serviceProvider)
: base(config.CronExpression, config.TimeZoneInfo, serviceProvider)
{
}
public override Task DoWork(IServiceScope scope, CancellationToken cancellationToken)
{
Serilog.Log.Information("Verified Database!");
return Task.CompletedTask;
}
}
public class TimerSendEmail : CronJobExtensions
{
public TimerSendEmail(IScheduleConfig<TimerSendEmail> config, IServiceProvider serviceProvider)
: base(config.CronExpression, config.TimeZoneInfo, serviceProvider)
{
}
public override Task DoWork(IServiceScope scope, CancellationToken cancellationToken)
{
Serilog.Log.Information("Email sent!");
return Task.CompletedTask;
}
}
Testing
To test the schedules, run the API and analyze the logs generated by Serilog in the application console:
It’s possible to see that the task scheduling that checks the database is triggered every second, while the scheduling that sends emails is triggered every minute.
Conclusion
Cronos is a library for parsing Cron expressions and calculating the next occurrences, with it, it is possible to schedule important background tasks, examples: Perform database maintenance every morning, capture data from a source from time to time, creation of alerts, etc.