Przejdź do treści

FluentScheduler oraz Coravel

  • przez

W tym poście chciałbym pokazać jak możemy użyć FluentScheduler oraz Coravel do harmonogramowania zadań.

FluentScheduler

Standardowo musimy dodać bibliotekę FluentScheduler za pomocą NuGet. Jeśli mamy jakieś proste zadanie to możemy je zdefiniować tak:

JobManager.AddJob(
    () => Console.WriteLine($"[{DateTime.UtcNow}] {Guid.NewGuid()} job"),
    (s) => s.ToRunEvery(5).Seconds());
Console.WriteLine($"Press enter to finish{Environment.NewLine}");
Console.ReadLine();

Po uruchomieniu zobaczymy coś podobnego do tego poniżej:

Press enter to finish

[8/31/2025 3:24:28 PM] 8671619c-809e-4386-a664-a60b97e0eb98 job
[8/31/2025 3:24:33 PM] 58801686-01ad-41f2-bb9c-1d0c7beaaaa6 job
[8/31/2025 3:24:38 PM] e9550846-346b-42e8-8de2-fad695c38964 job
[8/31/2025 3:24:43 PM] 7416d671-704a-41c4-a5b6-9bcf5d39d3a7 job

Natomiast jeśli zadanie jest bardziej skomplikowane lub mamy więcej zadań to moim zdaniem lepiej zrobić to tak:

using FluentScheduler;

namespace FluentSchedulerDemo;

public class MyJob : IJob
{
    public void Execute()
    {
        Console.WriteLine($"[{DateTime.UtcNow}] {Guid.NewGuid()} job");
    }
}
using FluentScheduler;
using FluentSchedulerDemo;

var registry = new Registry();
registry.Schedule<MyJob>().ToRunNow().AndEvery(7).Seconds();

JobManager.Initialize(registry);

Console.WriteLine($"Press enter to finish{Environment.NewLine}");
Console.ReadLine();

Rezultat jest podobny:

Press enter to finish

[8/31/2025 3:41:11 PM] a8b247ca-1d7c-44e4-b9d3-acb7bd7f7cde job
[8/31/2025 3:41:18 PM] 78fbd1df-e88c-44d7-90ab-0f796659ee71 job
[8/31/2025 3:41:25 PM] 725d3519-4515-4fee-b26f-8aa4f148a304 job

Możemy też uruchamiać zadania sekwencyjnie. Aby to pokazać dodam kolejne zadanie:

using FluentScheduler;

namespace FluentSchedulerDemo;

public class MyAnotherJob : IJob
{
    public void Execute()
    {
        Console.WriteLine($"[{DateTime.UtcNow}] {Guid.NewGuid()} another job");
    }
}

Jeszcze musimy zmienić konfigurację tak jak poniżej:

using FluentScheduler;
using FluentSchedulerDemo;

var registry = new Registry();
registry.Schedule<MyJob>().AndThen<MyAnotherJob>().ToRunNow().AndEvery(4).Seconds();

JobManager.Initialize(registry);

Console.WriteLine($"Press enter to finish{Environment.NewLine}");
Console.ReadLine();

Po uruchomieniu zobaczymy coś podobnego do tego poniżej:

[8/31/2025 3:45:30 PM] 68904275-8436-4b32-88ff-20681cadcdf6 job
[8/31/2025 3:45:30 PM] 066a0582-d87b-4ca7-bbf1-6b4d2a4bbc18 another job
[8/31/2025 3:45:34 PM] ce660aca-e5c1-4cc4-878c-b48d0ed0e147 job
[8/31/2025 3:45:34 PM] 410debbb-3920-4f7d-8994-318822c21f56 another job
[8/31/2025 3:45:38 PM] 248fa3ec-8577-4f5c-8d23-7ee8e3bc2cba job
[8/31/2025 3:45:38 PM] 013b4e7e-55bd-4abf-9394-514d68bfa0f1 another job

Możemy też dodać monitoring do naszych zadań:

JobManager.JobException += info => Console.WriteLine("An error just happened with a scheduled job: " + info.Exception);
JobManager.JobStart += info => Console.WriteLine($"{info.Name}: started");
JobManager.JobEnd += info => Console.WriteLine($"{info.Name}: ended ({info.Duration})");

Przykładowy rezultat:

MyJob: started
Press enter to finish

[8/31/2025 3:51:05 PM] b5105475-92f6-4fa6-ab05-e8a3a9271ed2 job
[8/31/2025 3:51:05 PM] d55b1b99-9a11-4596-911a-15eef46d4ef3 another job
MyJob: ended (00:00:00.0202209)
MyJob: started
[8/31/2025 3:51:09 PM] 5c018fad-bd31-4721-a3d6-e4e6db3b3cc3 job
[8/31/2025 3:51:09 PM] 38df0b5f-2cce-4328-8101-b3dc64320459 another job
MyJob: ended (00:00:00.0004210)
MyJob: started
[8/31/2025 3:51:13 PM] 1a31e1b2-0cdc-4f61-a5d9-e8c53d0c72ba job
[8/31/2025 3:51:13 PM] d0ba2107-0dad-4436-8933-358f8eb63bdb another job
MyJob: ended (00:00:00.0002949)
MyJob: started
[8/31/2025 3:51:17 PM] 0a600cfa-c3e0-4335-b098-549ab1157395 job
[8/31/2025 3:51:17 PM] b44ca8f4-a584-472d-8fe8-1e8af3aaeb0a another job
MyJob: ended (00:00:00.0002907)
MyJob: started
[8/31/2025 3:51:21 PM] 285a045b-8cf2-4c22-8b1d-8fb7a9351e1a job
[8/31/2025 3:51:21 PM] 14a11013-cbcb-428e-850c-d3c29ef9161f another job
MyJob: ended (00:00:00.0004109)
MyJob: started
[8/31/2025 3:51:25 PM] 6be76f6e-d17a-45e6-8426-64a259a33ccf job
[8/31/2025 3:51:25 PM] 3e95bde4-4c90-4cfd-88db-bd63796c8b12 another job
MyJob: ended (00:00:00.0002849)

Ta biblioteka zawiera wiele możliwości konfiguracji, które możesz znaleźć tutaj.

Coravel

Coravel to lekka biblioteka dla .NET, która ułatwia dodawanie zadań w tle i harmonogramów (schedulerów) bez potrzeby używania zewnętrznych usług. Pozwala w prosty sposób tworzyć zadania cykliczne, kolejki zadań (queue), cache, eventy i wysyłanie e-maili — wszystko oparte na wstrzykiwaniu zależności (Dependency Injection).

Zaczynamy podobnie, czyli musimy dodać bibliotekę:

dotnet add package coravel

Tym razem stworzę proste web api. Moim zdaniem tutaj jest przewaga FluentScheduler, bo ta biblioteka łatwiej daje się używać w aplikacjach konsolowych. Po utowrzeniu projektu konfigurujemy Coravel w Program.cs

using Coravel;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScheduler();
builder.Services.AddTransient<MyJob>();

var app = builder.Build();

app.Services.UseScheduler(scheduler =>
{
    scheduler.Schedule<MyJob>()
    .EveryMinute();
});

app.Run();

Nasze zadanie definiujemy tak jak poniżej:

using Coravel.Invocable;

public class MyJob : IInvocable
{
    public Task Invoke()
    {
        Console.WriteLine($"[{DateTime.UtcNow}] {Guid.NewGuid()} job");
        return Task.CompletedTask;
    }
}

Rezultat będzie podobny do tego poniżej:

[10/19/2025 7:15:00 PM] 46b6c770-dc8f-4155-847a-b05ca501d623 job
[10/19/2025 7:16:00 PM] 9dd992cd-00e2-47d9-b676-a8815ec478f7 job
[10/19/2025 7:17:00 PM] 6843d24e-fd64-49d6-8654-c3fc0319d310 job
[10/19/2025 7:18:00 PM] 59f156c1-9770-4a87-a270-2b359fbfba39 job
[10/19/2025 7:19:00 PM] 996af248-97c0-4acd-9db8-605e7a1a3112 job
[10/19/2025 7:20:00 PM] 6d13ee48-ad47-435d-9d97-1728e4ee2fe8 job
[10/19/2025 7:21:00 PM] 692cadaa-9684-4a62-82b2-242c4fa01bb5 job

Podsumowanie

Moim zdaniem obie biblioteki są warte uwagi. Pozwalają na konfigurację w łatwy sposób i oszczędzamy czas. Oczywiście możemy pisać sami podobne schedulery za pomocą IHostedService / BackgroundService i sam tak wielokrotnie robiłem. Ale może warto rozważyć te dwie biblioteki (lub inne Hangfire, Quarz.NET itp.). Oczywiście im mniej zależności tym mniej niespodzianek w utrzymaniu projektu, który mamy zamiar utrzymywać kilka lat 😉