Search  
Always will be ready notify the world about expectations as easy as possible: job change page
Aug 26

Scheduling tasks with Cronos in .NET

Scheduling tasks with Cronos in .NET
Author:
Source:
Views:
949

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:

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.

Similar
Jun 1
Author: Akalanka Dissanayake
In the second part of our series, the focus shifts towards validating the security and reliability of our ASP.NET 8 Web API through comprehensive integration testing. Integration testing plays a critical role in ensuring that our authentication mechanisms work as...
Jan 22
Author: Jeslur Rahman
Health checks are crucial for monitoring the status of your applications and services. They provide a quick and automated way to verify that your application’s dependencies and components are running smoothly. This article will explore how to implement health checks...
Nov 29, 2023
Author: Rico Fritzsche
Streamline Your .NET 8 Projects with the Power of MediatR and Blazor In this article, I want to revisit how the Vertical Slice Architecture can be used. This article takes an in-depth look at feature slicing and its application to...
May 6
Author: Trevor McCubbin
Introduction As a passionate .NET developer, I find myself continuously seeking opportunities to expand my understanding of the .NET ecosystem. In this, my first article ever, I embark on a journey to explore the intricacies of .NET performance, with a...
Send message
Type
Email
Your name
*Message