Immediate.Jobs
0.5.0
dotnet add package Immediate.Jobs --version 0.5.0
NuGet\Install-Package Immediate.Jobs -Version 0.5.0
<PackageReference Include="Immediate.Jobs" Version="0.5.0" />
<PackageVersion Include="Immediate.Jobs" Version="0.5.0" />
<PackageReference Include="Immediate.Jobs" />
paket add Immediate.Jobs --version 0.5.0
#r "nuget: Immediate.Jobs, 0.5.0"
#:package Immediate.Jobs@0.5.0
#addin nuget:?package=Immediate.Jobs&version=0.5.0
#tool nuget:?package=Immediate.Jobs&version=0.5.0
Immediate.Jobs
Immediate.Jobs is a reflection-free background job scheduler for .NET 8+ built on
Immediate.Handlers. A job is a [Handler] whose request can
also be durably enqueued; a Roslyn source generator emits its typed scheduler, payload metadata, and dependency-injection
registrations at compile time.
Immediate.Jobs provides at-least-once delivery. Every handler that performs externally visible work must be idempotent. The in-memory provider is single-node, non-durable, and intended only for development, tests, and non-critical work.
Quick start
Install the core package:
dotnet add package Immediate.Jobs --prerelease
Define a job using the same handler model as Immediate.Handlers, then inject its generated scheduler:
using Immediate.Handlers.Shared;
using Immediate.Jobs.Shared;
[Handler, Job(Name = "send-welcome-email", MaxAttempts = 5)]
public sealed partial class SendWelcomeEmail(IEmailSender sender)
{
public sealed record Payload(Guid UserId, string Template);
private ValueTask HandleAsync(Payload payload, CancellationToken cancellationToken) =>
new(sender.SendAsync(payload.UserId, payload.Template, cancellationToken));
}
public sealed class SignupService(SendWelcomeEmail.Scheduler welcomeEmail)
{
public ValueTask<JobHandle> EnqueueAsync(Guid userId, CancellationToken cancellationToken) =>
welcomeEmail.EnqueueAsync(new(userId, "v2"), cancellationToken);
}
Register the generated handlers and jobs methods in Program.cs, in that order. For an assembly named MyApp, these
are AddMyAppHandlers() and AddMyAppJobs().
Scheduling and handles
Generated schedulers use one method name for relative and absolute scheduling. Pass a TimeSpan for a delay or a
DateTimeOffset for a due time:
JobHandle immediate = await welcomeEmail.EnqueueAsync(
new(userId, "v2"),
cancellationToken);
JobHandle delayed = await welcomeEmail.ScheduleAsync(
new(userId, "reminder"),
TimeSpan.FromHours(1),
cancellationToken);
JobHandle scheduled = await welcomeEmail.ScheduleAsync(
new(userId, "tomorrow"),
DateTimeOffset.UtcNow.AddDays(1),
cancellationToken);
JobHandle and BatchHandle keep job and batch identifiers separate in application code. Read their string values
from JobHandle.JobHandle and BatchHandle.BatchHandle. Use JobHandle.FromString(...) or BatchHandle.FromString(...) when
an identifier enters through a route, message, or another string-based boundary. Both handle types serialize to their
string value with System.Text.Json.
Batches and continuations
Build an atomic workflow with BatchScheduler and the generated scheduler methods. Enqueue creates a root job.
ScheduleAfter accepts one or several earlier BatchJobHandle values, which supports fan-out and fan-in without
persisting a partial graph:
await using var batch = batches.Begin();
var received = receiveOrder.Enqueue(new(orderId), batch);
var inventory = reserveInventory.ScheduleAfter(new(orderId), received);
var payment = capturePayment.ScheduleAfter(new(orderId), received);
var dispatch = dispatchOrder.ScheduleAfter(
new(orderId),
[inventory, payment]);
BatchHandle batchHandle = await batch.CommitAsync(cancellationToken);
JobHandle dispatchHandle = dispatch.JobHandle;
The batch builder is short-lived and is not thread-safe. Nothing reaches storage until CommitAsync succeeds. A
BatchJobHandle exposes its JobHandle only after that commit. Disposing an uncommitted batch discards its buffered jobs.
Use ScheduleAfterAsync for a continuation created outside an open batch. Its parent may be a JobHandle or
BatchHandle, and a list of handles creates a fan-in dependency. Delays start when the required parent outcome is
reached:
JobHandle followUp = await sendReceipt.ScheduleAfterAsync(
new(orderId),
batchHandle,
TimeSpan.FromMinutes(5),
cancellationToken: cancellationToken);
Jobs can also extend their current workflow. Implement IJobRequest on the payload to receive JobDetails, then call
ScheduleAfter from the handler. The runtime writes the buffered additions only when that attempt succeeds, so a retry
does not leave duplicate branches:
public sealed record Payload(Guid OrderId) : IJobRequest
{
public JobDetails? JobDetails { get; set; }
}
private ValueTask HandleAsync(Payload payload, CancellationToken cancellationToken)
{
var currentJob = payload.JobDetails
?? throw new InvalidOperationException("Job details were not populated.");
recordAssessment.ScheduleAfter(
new(payload.OrderId),
currentJob,
ContinuationOptions.BeforeContinuations);
return ValueTask.CompletedTask;
}
BeforeContinuations makes existing waiters depend on the new job. BesideContinuations adds a parallel branch, and
Detached schedules outside the current batch. The asynchronous EnqueueAsync and ScheduleAsync overloads that
accept JobDetails persist a new member in the current batch immediately when the work must not wait for the running
attempt to finish.
Packages
Each package has focused installation and configuration guidance:
| Package | Purpose |
|---|---|
| Immediate.Jobs | Core scheduler, source generator, execution engine, and in-memory provider |
| Immediate.Jobs.EntityFrameworkCore | Durable EF Core storage for PostgreSQL, SQLite, and SQL Server |
| Immediate.Jobs.LinqToDB | Durable LinqToDB storage for PostgreSQL, SQLite, and SQL Server |
| Immediate.Jobs.Redis | Distributed Redis queue and recurring storage |
| Immediate.Jobs.Dashboard | Embedded monitoring dashboard and HTTP API |
| Immediate.Jobs.Testing | Deterministic test harness, test doubles, assertions, and provider conformance tests |
| Immediate.Jobs.NodaTime | NodaTime scheduling overloads and job payload serialization |
The SQL providers support batches, continuations, and fair scheduling between tenant groups. Redis does not support those features in the current release. See Queues and fairness and Batches and continuations for details.
Samples and documentation
The online documentation covers the complete API. The Aspire sample runs the EF Core provider against an Aspire-managed PostgreSQL container, exports logs, traces, metrics, and health status, and exposes the Immediate.Jobs dashboard.
Benchmarks
The repository includes BenchmarkDotNet comparisons with TickerQ, Hangfire MemoryStorage, and Quartz.NET. In addition to enqueue, direct dispatch, and startup, the suite covers concurrent throughput, cron expressions, delegate invocation, job creation, serialization, and startup registration. These are microbenchmarks of deliberately different framework APIs—not end-to-end durability or worker-latency measurements—so run them on the deployment target before drawing conclusions.
Results
The tables below are the historical ShortRun results from 21 July 2026: BenchmarkDotNet 0.15.8, .NET 8.0.22 Arm64
RyuJIT, Apple M3 Pro with 12 cores, macOS 26.5. Each result uses one launch, three warmup iterations, and three
measurement iterations. Ratios use Immediate.Jobs as the baseline. The expanded TickerQ suite targets .NET 10 and does
not yet have checked-in results.
EnqueueAsync
| Framework | Mean | Ratio | Allocated | Allocation ratio |
|---|---|---|---|---|
| Immediate.Jobs | 3.796 μs | 1.00 | 5.07 KB | 1.00 |
| Hangfire | 16.122 μs | 4.25 | 14.49 KB | 2.86 |
| Quartz.NET | 17.288 μs | 4.56 | 6.34 KB | 1.25 |
Direct dispatch
| Framework | Mean | Ratio | Allocated |
|---|---|---|---|
| Immediate.Jobs | 0.9994 ns | 1.00 | 0 B |
| Hangfire | 28.0701 ns | 28.09 | 32 B |
| Quartz.NET | 0.0521 ns | 0.05 | 0 B |
The Immediate.Jobs and Quartz.NET dispatch operations are effectively below the benchmark's reliable measurement floor. Treat their sub-nanosecond values as "no measurable dispatch overhead" rather than literal timing precision.
Scheduler construction
| Framework | Mean | Ratio | Allocated | Allocation ratio |
|---|---|---|---|---|
| Immediate.Jobs | 393.60 ns | 1.00 | 649 B | 1.00 |
| Hangfire | 7,765.75 ns | 19.77 | 3,104 B | 4.78 |
| Quartz.NET | 11.67 ns | 0.03 | 136 B | 0.21 |
The checked-in reports are available for enqueue, direct dispatch, and scheduler construction.
Run the complete suite with:
dotnet run --project benchmarks/Immediate.Jobs.Benchmarks -c Release -- --filter '*'
License
Immediate.Jobs is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
-
net10.0
- Cronos (>= 0.13.0)
- Immediate.Handlers (>= 4.0.0)
- Immediate.Validations (>= 3.6.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.11)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
-
net11.0
- Cronos (>= 0.13.0)
- Immediate.Handlers (>= 4.0.0)
- Immediate.Validations (>= 3.6.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 11.0.0-preview.7.26381.103)
-
net8.0
- Cronos (>= 0.13.0)
- Immediate.Handlers (>= 4.0.0)
- Immediate.Validations (>= 3.6.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.30)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
-
net9.0
- Cronos (>= 0.13.0)
- Immediate.Handlers (>= 4.0.0)
- Immediate.Validations (>= 3.6.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.19)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 9.0.19)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.19)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.19)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.