Rystem.BackgroundJob
10.0.7
dotnet add package Rystem.BackgroundJob --version 10.0.7
NuGet\Install-Package Rystem.BackgroundJob -Version 10.0.7
<PackageReference Include="Rystem.BackgroundJob" Version="10.0.7" />
<PackageVersion Include="Rystem.BackgroundJob" Version="10.0.7" />
<PackageReference Include="Rystem.BackgroundJob" />
paket add Rystem.BackgroundJob --version 10.0.7
#r "nuget: Rystem.BackgroundJob, 10.0.7"
#:package Rystem.BackgroundJob@10.0.7
#addin nuget:?package=Rystem.BackgroundJob&version=10.0.7
#tool nuget:?package=Rystem.BackgroundJob&version=10.0.7
What is Rystem?
Rystem.BackgroundJob
Rystem.BackgroundJob adds CRON-based recurring jobs on top of the Rystem DI stack.
The package is designed for applications that want small recurring jobs without introducing a heavier external scheduler. Jobs are registered through dependency injection, started during DI warm-up, and executed according to standard 5-field or 6-field CRON expressions through Cronos.
It is most useful for:
- recurring maintenance jobs
- polling and synchronization tasks
- cleanup or archival work
- lightweight scheduled automation inside ASP.NET Core or generic host applications
The best source-backed examples for this package are the package implementation itself plus the sample web app in src/Extensions/BackgroundJob/Test/Rystem.BackgroundJob.WebApp.
Resources
- Complete Documentation: https://rystem.net
- MCP Server for AI: https://rystem.cloud/mcp
- Discord Community: https://discord.gg/tkWvy4WPjt
- Support the Project: https://www.buymeacoffee.com/keyserdsoze
Installation
dotnet add package Rystem.BackgroundJob
The current 10.x package targets net10.0 and builds on top of:
CronosRystem.Concurrency- the warm-up flow from
Rystem.DependencyInjection
Package Architecture
The package is intentionally compact and revolves around four pieces.
| Piece | Purpose |
|---|---|
IBackgroundJob |
Contract implemented by each scheduled job |
BackgroundJobOptions |
CRON, startup behavior, and logical key |
IBackgroundJobManager |
Scheduler abstraction responsible for running jobs |
AddBackgroundJob<TJob> |
DI entry point that registers the job and wires startup through warm-up |
At a high level, the flow is:
- register a job with
AddBackgroundJob<TJob>(...) - the package adds the job as a transient service
- warm-up starts the schedule after the provider is built
- the manager computes the next CRON occurrence and arms a timer
- each tick executes the job and schedules the next one
Table of Contents
- Package Architecture
- Implement a Job
- Register a Job
- BackgroundJobOptions
- Scheduling and Warm-up
- Multiple Registrations of the Same Job Type
- Custom Job Manager
- Repository Examples
Implement a Job
Jobs implement IBackgroundJob.
The public job types live in the System.Timers namespace, so the usual starting point is:
using System.Timers;
Then implement the job itself:
using System.Timers;
public sealed class ReportJob : IBackgroundJob
{
private readonly ILogger<ReportJob> _logger;
public ReportJob(ILogger<ReportJob> logger)
{
_logger = logger;
}
public async Task ActionToDoAsync(CancellationToken cancellationToken = default)
{
_logger.LogInformation("Report job running at {Time}", DateTime.UtcNow);
await Task.Delay(10, cancellationToken);
}
public Task OnException(Exception exception)
{
_logger.LogError(exception, "Report job failed");
return Task.CompletedTask;
}
}
IBackgroundJob contract
The interface is intentionally small:
public interface IBackgroundJob
{
Task ActionToDoAsync(CancellationToken cancellationToken = default);
Task OnException(Exception exception);
}
ActionToDoAsync(...)contains the recurring workOnException(...)is called whenActionToDoAsync(...)throws
If you want the scheduler loop to keep going, OnException(...) should usually log or handle the exception rather than rethrow it.
Dependency injection behavior
AddBackgroundJob<TJob>(...) registers the job as Transient.
The sample app in src/Extensions/BackgroundJob/Test/Rystem.BackgroundJob.WebApp/BackgroundJob.cs shows the intended lifetime behavior clearly:
- singleton dependencies stay stable across executions
- scoped dependencies are shared inside one execution but change across later executions
- transient dependencies are recreated even inside the same execution when resolved twice
That makes constructor injection work the same way it does in the rest of the application.
Register a Job
Register the job during service setup:
builder.Services.AddBackgroundJob<ReportJob>(options =>
{
options.Cron = "*/5 * * * *";
options.RunImmediately = true;
});
What AddBackgroundJob<TJob>(...) does internally:
- registers the default
IBackgroundJobManagerwithTryAddSingleton(...) - enables the Rystem lock service with
AddLock() - registers
TJobas transient - creates effective options
- schedules startup through
AddWarmUp(...)
So this package depends on the same warm-up lifecycle documented in src/Core/Rystem.DependencyInjection/README.md.
BackgroundJobOptions
BackgroundJobOptions contains the scheduling settings:
public sealed class BackgroundJobOptions
{
public string? Key { get; set; }
public bool RunImmediately { get; set; }
public string Cron { get; set; } = "* * * * *";
}
In practice, AddBackgroundJob<TJob>(...) starts from these effective defaults before your configuration delegate runs:
| Property | Effective default in AddBackgroundJob(...) |
Purpose |
|---|---|---|
Key |
random GUID | Distinguishes one registration from another |
RunImmediately |
false |
Executes once during warm-up before the timer begins |
Cron |
"0 1 * * *" |
Schedules the job daily at 01:00 UTC |
That distinction matters because the raw BackgroundJobOptions class initializes Cron to "* * * * *", while AddBackgroundJob(...) overwrites the starting default to "0 1 * * *" unless you set a different value.
Scheduling and Warm-up
Warm-up starts the scheduler
Jobs do not start automatically just because they were registered. They start when warm-up runs.
var app = builder.Build();
await app.Services.WarmUpAsync();
app.Run();
Without WarmUpAsync(), the recurring timers are never started.
RunImmediately
If RunImmediately is true, the manager executes ActionToDoAsync(...) once during warm-up before creating the first scheduled timer.
builder.Services.AddBackgroundJob<ReportJob>(options =>
{
options.Cron = "0 */2 * * *";
options.RunImmediately = true;
});
This is useful when the first execution should happen during startup instead of waiting for the first future CRON occurrence.
CRON format
The package uses Cronos and supports both standard and second-based expressions. The manager detects the format by counting space-separated fields.
*/1 * * * *
- 5 fields → standard CRON format
- 6 fields → CRON format with seconds
Examples:
*/1 * * * *
0 */2 * * *
0 9 * * 1-5
*/30 * * * * *
0 0 9 * * 1-5
For quick validation, crontab.guru is still a convenient companion for the 5-field form.
Multiple Registrations of the Same Job Type
You can register the same job type more than once.
builder.Services.AddBackgroundJob<ReportJob>(options =>
{
options.Key = "daily";
options.Cron = "0 8 * * *";
});
builder.Services.AddBackgroundJob<ReportJob>(options =>
{
options.Key = "weekly";
options.Cron = "0 8 * * 1";
});
The built-in manager uses a key shaped like:
BackgroundWork_{options.Key}_{jobTypeFullName}
So multiple registrations stay independent as long as they do not collide on both job type and key.
If you do not set Key, the package generates a GUID automatically, which already keeps separate registrations distinct.
Custom Job Manager
If you need different scheduling or execution semantics, replace the default manager.
builder.Services.AddBackgroundJobManager<MyCustomJobManager>();
Your implementation must satisfy:
public interface IBackgroundJobManager
{
Task RunAsync(
IBackgroundJob job,
BackgroundJobOptions options,
Func<IBackgroundJob>? factory = null,
CancellationToken cancellationToken = default);
}
The optional factory parameter is the hook the default manager uses to recreate jobs for later scheduled executions.
Important: call AddBackgroundJobManager<TJobManager>() before any AddBackgroundJob(...) registration, because the built-in manager is registered with TryAddSingleton(...).
The built-in BackgroundJobManager also uses the Rystem lock service to prevent concurrent timer updates for the same logical job key.
Repository Examples
The most useful references for this package are:
- Package registration entry point: src/Extensions/BackgroundJob/Rystem.BackgroundJob/ServiceCollectionExtensions/ServiceCollectionExtensions.cs
- Built-in scheduler manager: src/Extensions/BackgroundJob/Rystem.BackgroundJob/BackgroundJob/BackgroundJobManager.cs
- Background job contract: src/Extensions/BackgroundJob/Rystem.BackgroundJob/Interfaces/IBackgroundJob.cs
- Sample application startup: src/Extensions/BackgroundJob/Test/Rystem.BackgroundJob.WebApp/Program.cs
- Sample job showing DI lifetime behavior: src/Extensions/BackgroundJob/Test/Rystem.BackgroundJob.WebApp/BackgroundJob.cs
This README stays focused because Rystem.BackgroundJob is a narrow package: one job contract, one manager abstraction, one DI registration path, and a CRON-driven execution loop.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- Cronos (>= 0.11.1)
- Rystem.Concurrency (>= 10.0.7)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Rystem.BackgroundJob:
| Package | Downloads |
|---|---|
|
Rystem.Queue
Rystem. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.7 | 60 | 3/26/2026 |
| 10.0.6 | 148,373 | 3/3/2026 |
| 10.0.5 | 168 | 2/22/2026 |
| 10.0.4 | 182 | 2/9/2026 |
| 10.0.3 | 147,979 | 1/28/2026 |
| 10.0.1 | 209,148 | 11/12/2025 |
| 9.1.3 | 363 | 9/2/2025 |
| 9.1.2 | 764,565 | 5/29/2025 |
| 9.1.1 | 97,866 | 5/2/2025 |
| 9.0.32 | 186,720 | 4/15/2025 |
| 9.0.31 | 5,851 | 4/2/2025 |
| 9.0.30 | 88,882 | 3/26/2025 |
| 9.0.29 | 9,031 | 3/18/2025 |
| 9.0.28 | 289 | 3/17/2025 |
| 9.0.27 | 295 | 3/16/2025 |
| 9.0.26 | 284 | 3/13/2025 |
| 9.0.25 | 52,174 | 3/9/2025 |
| 9.0.21 | 364 | 3/6/2025 |
| 9.0.20 | 19,619 | 3/6/2025 |
| 9.0.19 | 339 | 3/6/2025 |