Pragmatic.Jobs
AOT-safe background job scheduling for .NET 10 — recurring cron jobs, delayed fire-and-forget, continuation chains, and lease-based distributed locking, all source-generated at compile time.
The Problem
Section titled “The Problem”Background jobs in .NET usually mean Hangfire or Quartz.NET. Both rely on runtime reflection for job discovery, serialization, and invocation, require separate dashboards/storage, and keep retry config outside the job definition — so the job class has no idea how it’ll be scheduled, retried, or timed out.
// Without Pragmatic: config separate from the jobRecurringJob.AddOrUpdate<DailyReportJob>("daily-report", x => x.Execute(), "0 2 * * *");GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3 }); // global, not per-jobThe Solution
Section titled “The Solution”Declare scheduling, retry, and timeout on the job class. The generator produces the invoker with an inline retry loop, linked cancellation tokens, telemetry, and DI wiring — zero reflection.
[RecurringJob("0 2 * * *", Id = "daily-report")][Retry(MaxAttempts = 3, Strategy = BackoffStrategy.ExponentialWithJitter, BaseDelayMs = 1000)][Timeout(TimeoutSeconds = 600)]public sealed partial class DailyReportJob(IReportService reports) : IJob{ public async Task ExecuteAsync(JobContext context, CancellationToken ct) => await reports.GenerateDailyAsync(context.ScheduledAt.Date, ct);}That’s the whole job. The generator emits the invoker (retry + timeout + telemetry), the DI
registration, an AOT-safe type registry (no Type.GetType), the recurring definition, and metadata.
Installation
Section titled “Installation”dotnet add package Pragmatic.Jobsdotnet add package Pragmatic.Jobs.EFCore # optional: durable, distributed job storedotnet add package Pragmatic.SourceGenerator # the unified analyzerQuick Start
Section titled “Quick Start”// A delayed, fire-and-forget job[Job][Retry(MaxAttempts = 5)]public sealed partial class SendWelcomeEmailJob(IEmailService email) : IJob<Guid>{ public async Task ExecuteAsync(Guid userId, JobContext context, CancellationToken ct) => await email.SendWelcomeAsync(userId, ct);}
// Schedule itawait scheduler.ScheduleAsync<SendWelcomeEmailJob, Guid>(userId, delay: TimeSpan.FromMinutes(5));Enable processing in the host with app.UseJobs(jobs => jobs.WithWorkerCount(2)). Full walkthrough:
Getting Started.
What you can declare
Section titled “What you can declare”[Job]/[RecurringJob(cron)]— fire-and-forget or recurring (cron) jobs.[Retry]/[Timeout]— per-job resilience, inline in the generated invoker.[ContinueWith<T>]— continuation chains.- EF Core persistence — durable jobs with lease-based distributed locking (one worker per job across instances).
- Messaging bridge —
Pragmatic.Messaging.Jobsfor scheduled message delivery.
Status
Section titled “Status”Recurring/delayed jobs, retry/timeout, continuations, EF Core persistence, and distributed locking are functional within the 0.8 preview. See the roadmap.
| Concepts | Job types, scheduler/store model, the invoker pipeline, continuation chains, distributed locking | | Getting Started | Your first recurring and delayed jobs, host wiring | | Common Mistakes | The most frequent job pitfalls | | Troubleshooting | Problem/solution guide with diagnostics |
Requirements
Section titled “Requirements”- .NET 10.0+
Pragmatic.SourceGeneratoranalyzer
License
Section titled “License”Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Jobs is licensed under the PolyForm Small Business 1.0.0 license (free for small businesses; commercial license above the threshold).