Runtime jobs
Schedule typed one-off jobs with a payload at runtime through IJobScheduler, and cancel them by id.
Beyond compile-time recurring jobs, you can enqueue one-off work at runtime with a typed payload.
The job type still carries [ScheduledJob] so the generator emits its descriptor and a direct
invocation delegate — but it usually omits a recurring schedule.
Defining a runtime job
A runtime job implements IScheduledJob<TPayload>:
using Elarion.Abstractions.Scheduling;
[ScheduledJob("emails.send")]
public sealed class SendEmailJob(IEmailSender sender) : IScheduledJob<SendEmailPayload> {
public async ValueTask ExecuteAsync(
SendEmailPayload payload,
IScheduledJobContext context,
CancellationToken ct) {
await sender.SendAsync(payload.To, payload.Subject, payload.Body, ct);
}
}
public sealed record SendEmailPayload {
public required string To { get; init; }
public required string Subject { get; init; }
public required string Body { get; init; }
}Scheduling and cancelling
Use the typed IJobScheduler API:
var handle = await scheduler.ScheduleAsync<SendEmailJob, SendEmailPayload>(
new SendEmailPayload {
To = "customer@example.com",
Subject = "Invoice",
Body = "Your invoice is ready."
},
timeProvider.GetUtcNow().AddMinutes(5),
ct);
await scheduler.CancelJobAsync(handle.JobId, ct);When a handler enqueues background work and needs to return an operation id, return handle.JobId.
That is the id callers pass back to CancelJobAsync.
JobId vs. RunId
These two identifiers serve different purposes:
JobIdidentifies the logical runtime job.CancelJobAsync(jobId, ct)is the user-facing cancellation API: it cancels a queued job, a waiting retry attempt, or the active attempt without the caller needing to know the current state.RunIdidentifies one scheduler attempt, for diagnostics and snapshots. With deferred retry, each attempt gets a newRunIdbut keeps the sameJobId.
CancelRunAsync(runId, ct) targets one concrete attempt — use it for operator/diagnostic workflows
acting on a specific run shown in a snapshot. Job code can also request cancellation through
IScheduledJobContext.RequestCancellation().
Resilience for runtime jobs
Runtime jobs can opt into scheduler-deferred retry, which releases scheduler concurrency between
attempts and exposes WaitingRetry status — the right model when another handler needs to observe
progress. See Resilience for EnqueueAsync with a policy
reference and ScheduledJobResilienceMode.DeferredRetry.