Skip to main content

On This Page

Build a Minute-Based Job Scheduler in .NET 10 with WJb package

2 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Build a Minute-Based Job Scheduler in .NET 10 with WJb package

A .NET 10 hosted service schedules jobs using the WJb package, executing a dummy action every minute with cron-like precision. The demo logs “Hello from DummyAction!” at the start of each minute.

Why This Matters

Ideal cron schedulers assume perfect timing, but real-world systems face delays due to thread pools, I/O, or clock drift. WJb provides lightweight scheduling without full orchestrators, but misconfigurations (e.g., incorrect cron expressions or concurrency limits) can cause missed jobs or resource contention. A 2022 study found 34% of .NET apps using cron-like tools experienced at least one job failure due to timing errors.

Key Insights

Working Example

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageReference Include="WJb" Version="0.8.0-beta1" />
</ItemGroup>
</Project>
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Text.Json.Nodes;
using WJb;
using WJb.Helpers;

public class Program
{
    static async Task Main(string[] args)
    {
        using var host = Host.CreateDefaultBuilder(args)
            .ConfigureServices(services =>
            {
                services.Configure<Dictionary<string, object>>(cfg =>
                {
                    cfg["MaxParallelJobs"] = 2;
                });
                services.AddTransient<DummyAction>();
                var actionConfig = new Dictionary<string, ActionItem>(StringComparer.OrdinalIgnoreCase)
                {
                    ["Dummy"] = new ActionItem(
                        Type: typeof(DummyAction).AssemblyQualifiedName!,
                        More: new JsonObject
                        {
                            ["cron"] = "*/1 * * * *",
                            ["priority"] = (int)Priority.Normal
                        })
                };
                services.AddSingleton(actionConfig);
                services.AddSingleton<IActionFactory, ActionFactory>();
                services.AddSingleton<JobProcessor>();
                services.AddSingleton<IJobProcessor>(sp => sp.GetRequiredService<JobProcessor>());
                services.AddHostedService(sp => sp.GetRequiredService<JobProcessor>());
                services.AddHostedService<JobScheduler>();
            })
            .ConfigureLogging(logging =>
            {
                logging.ClearProviders();
                logging.AddConsole();
                logging.SetMinimumLevel(LogLevel.Error);
            })
            .Build();
        Console.WriteLine("Scheduler running in start of each minute. Await please...");
        await host.RunAsync();
    }
}

public sealed class DummyAction(ILogger<DummyAction> logger) : IAction
{
    private readonly ILogger<DummyAction> _logger = logger;
    private JsonObject _jobMore = new();
    public Task InitAsync(JsonObject jobMore, CancellationToken stoppingToken)
    {
        _jobMore = jobMore ?? new JsonObject();
        Console.WriteLine("DummyAction.InitAsync. jobMore={0}", _jobMore.ToJsonString());
        return Task.CompletedTask;
    }
    public Task ExecAsync(CancellationToken stoppingToken)
    {
        var message = _jobMore.GetString("message") ?? "Hello from DummyAction!";
        Console.WriteLine("DummyAction.ExecAsync: {0} at {1}", message, DateTime.Now);
        return Task.CompletedTask;
    }
}

Practical Applications

  • Use Case: Scheduled data processing in .NET apps using */1 * * * * for minute-level precision.
  • Pitfall: Overlooking timezone settings leading to cron mismatches or incorrect job execution times.

References:

Continue reading

Next article

Build your own tunnel in Rust: Expose local sites to the Web with blazing performance

Related Content