Schedule / ScheduleAsync
Schedules a single train to run on a recurring basis. Schedule is used at startup configuration time; ScheduleAsync is used at runtime via ITraxScheduler.
Both use upsert semantics: if a manifest with the given externalId already exists, it is updated; otherwise a new one is created.
Signatures
Startup (SchedulerConfigurationBuilder, Recommended)
The input type TInput is inferred from the TTrain interface at configuration time, so only a single type parameter is needed:
public SchedulerConfigurationBuilder Schedule<TTrain>(
string externalId,
IManifestProperties input,
Schedule schedule,
Action<ScheduleOptions>? options = null
)
where TTrain : classThe method resolves TInput by reflecting on TTrain's IServiceTrain<TInput, TOutput> interface. If the provided input doesn't match the expected type, an InvalidOperationException is thrown at configuration time. The output type is not constrained: scheduled trains can return any output type, and the output is discarded for background jobs.
Startup (SchedulerConfigurationBuilder, Explicit Type Parameters)
The legacy three-type-parameter form is still available for backward compatibility:
public SchedulerConfigurationBuilder Schedule<TTrain, TInput, TOutput>(
string externalId,
TInput input,
Schedule schedule,
Action<ScheduleOptions>? options = null
)
where TTrain : IServiceTrain<TInput, TOutput>
where TInput : IManifestPropertiesRuntime (ITraxScheduler)
Task<Manifest> ScheduleAsync<TTrain, TInput, TOutput>(
string externalId,
TInput input,
Schedule schedule,
Action<ScheduleOptions>? options = null,
CancellationToken ct = default
)
where TTrain : IServiceTrain<TInput, TOutput>
where TInput : IManifestPropertiesType Parameters
| Type Parameter | Constraint | Description |
|---|---|---|
TTrain | class (inferred) / IServiceTrain<TInput, TOutput> (explicit) | The train interface type. Can implement IServiceTrain<TInput, TOutput> with any output type. The scheduler resolves the concrete implementation via TrainBus using the input type. The output is discarded for background jobs. |
TInput | IManifestProperties | Inferred at startup from TTrain's interface. The input type for the train. Must implement IManifestProperties (a marker interface) to enable serialization for scheduled job storage. Only required explicitly in the explicit three-type-param form and the runtime API. |
TOutput | (none) | The output type of the train. Not constrained; any output type is accepted. The output is discarded when the job completes. Only required explicitly in the explicit three-type-param form and the runtime API. |
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
externalId | string | Yes | (none) | A unique identifier for this scheduled job. Used for upsert semantics: if a manifest with this ID exists, it will be updated; otherwise a new one is created. Also used to reference this job in dependent scheduling (ThenInclude, Include). |
input | IManifestProperties (inferred) / TInput (explicit) | Yes | (none) | The input data that will be passed to the train on each execution. Serialized and stored in the manifest. With the inferred API, the concrete type is validated against the train's expected input type at configuration time. |
schedule | Schedule | Yes | (none) | The schedule definition, either interval-based or cron-based. Cron schedules support both 5-field (minute) and 6-field (second) granularity. Use Every or Cron helpers to create one. Supports .WithVariance(TimeSpan) to add random jitter. |
options | Action<ScheduleOptions>? | No | null | Optional callback to configure all scheduling options via a fluent builder. Includes manifest-level settings (Priority, Enabled, MaxRetries, Timeout), group-level settings (.Group(...) with MaxActiveJobs, Priority, Enabled), and batch settings (PrunePrefix). See ScheduleOptions below. |
ct | CancellationToken | No | default | Cancellation token (runtime API only). |
ScheduleOptions
The ScheduleOptions fluent builder consolidates all optional scheduling parameters:
Manifest-level methods
| Method | Description |
|---|---|
.Priority(int) | Dispatch priority (0-31) of every entry queued for this manifest. Higher values dispatched first, within the order set by group priority. Unstated, a new manifest takes 0 and an existing one keeps its value. |
.Enabled(bool) | Whether the manifest is enabled. Unstated, a new manifest is enabled and an existing one keeps its current state, so a runtime disable survives a restart. |
.MaxRetries(int) | Retries after the first run before dead-lettering: the manifest is dead-lettered when its failures within its failure window (FailureWindow, or the scheduler's FailureCountWindow) exceed it. 0 runs once and dead-letters on the first failure; the default 3 allows four attempts. Unstated, a new manifest takes the scheduler's DefaultMaxRetries (3) and an existing one keeps its value, including a runtime change. Throws ArgumentOutOfRangeException for a negative value. |
.FailureWindow(TimeSpan) | How far back this manifest's failed runs count toward its retry backoff and MaxRetries, in place of the scheduler's FailureCountWindow. Stored in whole seconds; throws ArgumentOutOfRangeException unless between one second and ten years. Written to an existing manifest only when stated, so a manifest that stops stating it keeps the window it has. |
.Timeout(TimeSpan) | Job execution timeout. Unstated, a new manifest uses the global DefaultJobTimeout and an existing one keeps its value. |
.OnMisfire(MisfirePolicy) | Misfire policy for missed runs. Unstated: the scheduler's DefaultMisfirePolicy (FireOnceNow). FireOnceNow fires immediately; DoNothing skips and waits for the next natural occurrence. Only applies to Cron and Interval types. |
.MisfireThreshold(TimeSpan) | Grace period before the misfire policy takes effect. Overrides the global DefaultMisfireThreshold. |
.Variance(TimeSpan) | Adds random jitter to the schedule. After each successful run, the next execution is delayed by [0, variance] seconds. Applies to Interval and Cron schedules only; on a dependent or one-off manifest it is ignored. If Schedule.WithVariance() is also set, the schedule-level value takes precedence. See Schedule Variance. |
.Exclude(Exclusion) | Adds an exclusion window. The manifest is skipped when any exclusion matches the current time. Multiple can be combined. Use Exclude.DaysOfWeek(...), Exclude.Dates(...), Exclude.DateRange(...), or Exclude.TimeWindow(...) factories. See Exclusion Windows. |
Group-level methods
| Method | Description |
|---|---|
.Group(string groupId) | Sets the manifest group name. Defaults to externalId when not set. |
.Group(string groupId, Action<ManifestGroupOptions>) | Sets group name and configures group dispatch settings. |
.Group(Action<ManifestGroupOptions>) | Configures group dispatch settings without changing the group name. |
ManifestGroupOptions
| Method | Description |
|---|---|
.MaxActiveJobs(int?) | Max concurrent active jobs for this group. null = no per-group limit. |
.Priority(int) | Group dispatch priority (0-31). Unstated, a new group takes the manifest's priority. |
.Enabled(bool) | Kill switch for the entire group. Unstated, a new group is enabled. |
Each group setting is written only when stated: an existing group keeps any setting its members leave unstated, including a change made from the dashboard. Members of one group that state different values for a setting fail AddScheduler at startup. See What a Restart Rewrites.
Returns
- Startup:
SchedulerConfigurationBuilder, for continued fluent chaining. - Runtime:
Task<Manifest>, the created or updated manifest record.
Examples
Startup Configuration (Recommended, Inferred Input Type)
services.AddTrax(trax => trax
.AddEffects(effects => effects.UsePostgres(connectionString))
.AddMediator(typeof(Program).Assembly)
.AddScheduler(scheduler => scheduler
.Schedule<ISyncTrain>(
"sync-daily",
new SyncInput { Source = "production" },
Cron.Daily(hour: 3),
options => options
.MaxRetries(5)
.Priority(20)
.Group("daily-syncs"))
)
);AddScheduler extends the builder AddMediator returns, so it comes after AddEffects and
AddMediator; called earlier, it does not compile. Only the train interface type is specified. The input type (SyncInput) is inferred from ISyncTrain : IServiceTrain<SyncInput, TOutput> and validated at configuration time. The output type is not constrained. It can be Unit or any other type, and the output is discarded for background jobs.
Runtime Scheduling
public class MyService(ITraxScheduler scheduler)
{
public async Task SetupSchedule()
{
var manifest = await scheduler.ScheduleAsync<ISyncTrain, SyncInput, Unit>(
"sync-on-demand",
new SyncInput { Source = "staging" },
Every.Hours(1),
options => options.Priority(15));
}
}Schedule Record
The Schedule record defines the timing for a scheduled manifest. Create instances via Every.*, Cron.*, or Schedule.FromInterval() / Schedule.FromCron().
Properties
| Property | Type | Description |
|---|---|---|
Type | ScheduleType | Interval or Cron. |
Interval | TimeSpan? | The interval between runs (Interval type only). At least one second: Schedule.FromInterval throws ArgumentOutOfRangeException for anything shorter. |
CronExpression | string? | The cron expression, evaluated in UTC (Cron type only). Schedule.FromCron parses it and throws FormatException when it is invalid or has no occurrence in the next ten years. A new cron first runs at its first occurrence after it is scheduled. |
Variance | TimeSpan? | Optional random jitter added after each successful run. |
Methods
| Method | Returns | Description |
|---|---|---|
WithVariance(TimeSpan variance) | Schedule | Returns a new Schedule with the specified variance. The next run after each success is delayed by a random [0, variance] duration. A Schedule is always Interval or Cron, so any schedule accepts it. A negative variance throws InvalidOperationException when the manifest is scheduled. |
// Interval with 2-minute jitter
var every5Minutes = Every.Minutes(5).WithVariance(TimeSpan.FromMinutes(2));
// Cron with 30-minute jitter
var dailyAt3 = Cron.Daily(3).WithVariance(TimeSpan.FromMinutes(30));Remarks
- At startup, manifests are not created immediately. They are captured and seeded when the application starts via
SchedulerStartupService. - At runtime,
ScheduleAsynccreates/updates the manifest in the database immediately. - The
externalIdis the primary key for upsert logic. Changing it creates a new manifest rather than updating the existing one. - If the train type is not registered in the
TrainRegistry(viaAddMediator), anInvalidOperationExceptionis thrown. - The group is determined by
.Group(...)onScheduleOptions. When not specified, it defaults to theexternalId. Groups are auto-created (upserted by name) during scheduling. Orphaned groups are cleaned up on startup. - For one-off jobs that should run once and auto-disable, use ScheduleOnceAsync instead of
ScheduleAsync. It creates a manifest withScheduleType.Onceand needs noScheduleobject. See Delayed / One-Off Jobs for details.