Version v0.1 · dotnet
First Job Example
Detailed first-job walkthrough including attribute options and enqueue versus schedule behavior.
First Job Example
This walkthrough shows a realistic first job, all options in both job attributes, and why DurableStack separates enqueue metadata from recurring schedule metadata.
Example job
using DurableStack.Core;
using DurableStack.Core.Abstractions;
using DurableStack.Core.Models;
using DurableStack.Hosting.DependencyInjection;
[DurableJob(
Name = "invoice-sync",
MaxAttempts = 5,
RetryBehavior = RetryBehavior.Backoff,
RetryInitialDelaySeconds = 10)]
[RecurringJob(
"*/5 * * * *",
TimeZone = "UTC",
Enabled = true,
AllowConcurrentRuns = false)]
public sealed class InvoiceSyncJob : IDurableJob
{
private readonly ILogger<InvoiceSyncJob> _logger;
public InvoiceSyncJob(ILogger<InvoiceSyncJob> logger)
{
_logger = logger;
}
public async Task ExecuteAsync(JobContext context, CancellationToken cancellationToken)
{
_logger.LogInformation(
"Invoice sync run {RunId} attempt {Attempt} started at {StartedUtc}",
context.RunId,
context.Attempt,
context.StartedAtUtc);
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
_logger.LogInformation("Invoice sync run {RunId} completed", context.RunId);
}
}
Why there are two attributes
DurableJob defines execution behavior
Name: stable job identity used for registration and operations.MaxAttempts: total attempts including initial execution.RetryBehavior:FixedDelayorBackoff.RetryInitialDelaySeconds: per-job retry baseline.
This attribute defines what happens when a run executes and fails.
RecurringJob defines schedule behavior
Cron: schedule cadence.TimeZone: IANA timezone used for cron evaluation.Enabled: whether schedule emits new runs.AllowConcurrentRuns: whether overlapping scheduled runs are allowed.
This attribute defines whether and how new runs are created over time.
Enqueue versus schedule
- Enqueue creates one run immediately.
- Recurring schedule materializes runs automatically when cron slots are due.
- A job can be enqueue-only (no
RecurringJob) or recurring (both attributes).
Enqueue the same job manually
using DurableStack.Core.Abstractions;
app.MapPost("/jobs/invoice-sync/run-now", async (
IDurableStackClient client,
CancellationToken cancellationToken) =>
{
var runId = await client.EnqueueAsync<InvoiceSyncJob>(cancellationToken: cancellationToken);
return Results.Accepted($"/runs/{runId}", new { runId });
});
With this route plus [RecurringJob], the same handler supports both:
- automatic recurring execution
- manual run-now execution
Practical guidance
- Keep
DurableJob.Namestable after production adoption. - Use
AllowConcurrentRuns = falsefor jobs that mutate shared resources. - Prefer
RetryBehavior.Backofffor third-party API dependencies. - Keep handlers idempotent so retries are safe.
Next step
After this page, review Getting Started for the recommended runtime configuration baseline.