See More

A very common reason is a wrong site baseUrl configuration.\n

Current configured baseUrl = / (default value)\n

We suggest trying baseUrl = \n\n',document.body.prepend(n);var e=document.getElementById("__docusaurus-base-url-issue-banner-suggestion-container"),s=window.location.pathname,o="/"===s.substr(-1)?s:s+"/";e.textContent=o}document.addEventListener("DOMContentLoaded",function(){void 0===window.docusaurus&&insertBanner()})

Skip to main content

Getting Started

ZeroAlloc.Scheduling is a background job scheduler for .NET 8 and .NET 10. You decorate a class with [Job], and the Roslyn source generator emits the executor, DI registration, and optional recurring startup for you at build time — no reflection, no convention scanning, no IServiceCollection.Scan.

Installation

dotnet add package ZeroAlloc.Scheduling
dotnet add package ZeroAlloc.Scheduling.InMemory

The generator runs as an analyzer:

<PackageReference Include="ZeroAlloc.Scheduling.Generator" Version="*"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

Your First Job

Step 1 — Define the job

Implement IJob and decorate with [Job].

using ZeroAlloc.Scheduling;

[Job]
public sealed class SendWelcomeEmailJob : IJob
{
public required string To { get; init; }

public async ValueTask ExecuteAsync(JobContext ctx, CancellationToken ct)
{
// ctx.JobId, ctx.Attempt, ctx.ScheduledAt are available
Console.WriteLine($"Sending welcome email to {To} (attempt {ctx.Attempt})");
await Task.Delay(100, ct); // simulate work
}
}

Step 2 — Register

The generator emits AddSendWelcomeEmailJob(). Call it alongside AddScheduling and your chosen backend.

builder.Services
.AddScheduling()
.AddSchedulingInMemory()
.AddSendWelcomeEmailJob();

Step 3 — Enqueue

Inject IScheduler and enqueue.

public class UserService(IScheduler scheduler)
{
public async Task RegisterAsync(string email, CancellationToken ct)
{
// ... create user ...
await scheduler.EnqueueAsync(new SendWelcomeEmailJob { To = email }, ct);
}
}

Step 4 — Build and run

dotnet run

The background worker polls the store every 5 seconds (configurable), claims pending jobs, and executes them. On failure it retries with exponential backoff up to DefaultMaxAttempts (default: 3), then dead-letters.

Recurring Jobs

Add Every or Cron to the attribute. The generator also emits an IHostedService that seeds the schedule on startup.

[Job(Every = Every.Hour)]
public sealed class PurgeExpiredSessionsJob : IJob
{
public async ValueTask ExecuteAsync(JobContext ctx, CancellationToken ct) { ... }
}

// Or with a custom cron expression:
[Job(Cron = "0 9 * * 1-5")] // weekdays at 09:00 UTC
public sealed class DailyDigestJob : IJob { ... }

Register the same way — AddPurgeExpiredSessionsJob() registers both the executor and the startup service.

Dashboard

Serve the built-in HTML dashboard with one line:

app.MapJobsDashboard("/jobs");

Open /jobs/ in your browser to see pending, running, succeeded, failed, and dead-lettered jobs with live auto-refresh.

Configuration

builder.Services.AddScheduling(opt =>
{
opt.PollingInterval = TimeSpan.FromSeconds(5); // default
opt.BatchSize = 20; // jobs per poll
opt.RetryBaseDelay = TimeSpan.FromSeconds(2); // exponential base
opt.DefaultMaxAttempts = 3; // global retry limit
});

Next Steps

GuideWhat you'll learn
Source GeneratorAll [Job] options, Every enum, generated code
BackendsSwitch from InMemory to EF Core or Redis
DashboardDashboard options, Blazor component
Mediator BridgeRoute jobs through ZeroAlloc.Mediator
PerformanceTuning batch size, polling interval, concurrency