.NET API
IJobHandler
Tracon.Abstractions.dllThe extension point that provides job execution logic for one handler key.
public interface IJobHandlerRemarks
Section titled “Remarks”Registered with AddJobHandler<T>(handlerKey): the key lives on
the registration, not on the type, so the same type can serve two keys and
a duplicate key is caught at startup. The background worker looks the key
up by exact, ordinal match, so registration order never decides
which handler runs — a consumer handler cannot shadow a built-in
one, and a built-in one cannot shadow a consumer’s.
Lifetime: the handler is registered scoped and
resolved from a fresh dependency-injection scope per execution. Scoped
dependencies are therefore safe to take in the constructor: two jobs
running in parallel, and two attempts of the same job, never share an
instance.
Tenant: runs under the ambient tenant — the worker
opens the job’s own tenant scope before calling
IJobHandler.ExecuteAsync, so an ITenantContext the handler
resolves reports JobRecord.TenantId, not the process default.
The record is also on JobContext.Job for a handler that
prefers to read it explicitly.
builder.Services.AddJobHandler<NightlyReportJobHandler>("contoso.nightly-report");Methods
Section titled “Methods”ExecuteAsync(JobContext, CancellationToken)
Section titled “ ExecuteAsync(JobContext, CancellationToken)”Executes the job.
ValueTask ExecuteAsync(JobContext context, CancellationToken cancellationToken = default)Parameters
Section titled “Parameters”context JobContext
The job context: record, items, reporting, and cancellation check.
cancellationToken CancellationToken
The cancellation token (triggered when the worker shuts down).
Returns
Section titled “Returns”The completion task.
Remarks
Section titled “Remarks”Execution is at-least-once, not exactly-once. The same job — the same JobContext.Job, with the SAME unfiltered JobContext.Items list — can reach IJobHandler.ExecuteAsync more than once: if the handler throws, the job is retried with IJobStore.ReleaseForRetryAsync (if the attempt limit is not exceeded) or marked as JobStatus.Failed; if the process crashes or the lease simply expires before the handler returns, another worker (or the same one) re-leases the SAME job and calls IJobHandler.ExecuteAsync again from scratch. Neither case resets item progress.
Because of this, a handler with side effects (sending an email,
charging a payment, calling an external API) MUST be idempotent, or
MUST check JobItemRecord.Status itself and skip any item
that is not JobItemStatus.Pending — see
JobContext.Items. The handlers Tracon ships all do
the latter, and the reusable JobHandlerContract in
Tracon.Testing.Contracts.Xunit asserts it.