Skip to content

Startup Tasks

Run application startup work after the host reports that it has started.

Overview

The Startup Tasks feature executes registered initialization work after IHostApplicationLifetime.ApplicationStarted fires. Use it for work such as development data seeding, resource checks or cache preparation that does not need to block IHostedService.StartAsync.

sequenceDiagram
    participant App as Application
    participant STS as StartupTasksService
    participant Task as StartupTask
    participant Behavior as TaskBehavior

    App->>STS: StartAsync()
    Note over STS: Wait for ApplicationStarted
    STS->>STS: Apply StartupDelay
    par For each enabled task
        STS->>Behavior: Execute
        Behavior->>Task: ExecuteAsync
        Task-->>Behavior: Complete
        Behavior-->>STS: Complete
    end
    STS-->>App: All tasks completed

Challenges

  • Avoiding hidden dependencies between concurrently started initialization tasks
  • Handling task failures gracefully
  • Configuring different behaviors for development and production environments
  • Controlling task execution timing and delays

Solution

The Startup Tasks feature provides:

  • service-level and task-level enablement and startup delays
  • per-task dependency-injection scopes
  • a behavior pipeline around each task
  • structured start, completion, duration and failure logs
  • optional process termination when a task fails

Key Features

  • Register class-based tasks or task factories with WithTask.
  • Enable or disable the service and individual tasks.
  • Apply global and per-task startup delays.
  • Wrap all tasks with retry, timeout, circuit-breaker, chaos or custom behaviors.
  • Run each enabled task in its own dependency-injection scope.
  • Cancel in-flight tasks during host shutdown and wait for up to ten seconds.

Architecture

AddStartupTasks registers StartupTasksService as a hosted service. Its StartAsync method registers a callback and returns immediately. After ApplicationStarted, the service applies the global delay, creates one asynchronous operation per enabled definition, resolves each task in a new scope and executes the global behavior chain around IStartupTask.ExecuteAsync.

Tasks currently run concurrently through Task.WhenAll. Order sorts definitions before their operations are started, but it does not create a dependency or sequential execution guarantee. MaxDegreeOfParallelism exists on StartupTaskServiceOptions, but the current service does not apply it. Do not use either option to coordinate tasks that depend on each other.

Use Cases

  • Database seeding for development environments
  • System configuration validation
  • Initial data caching
  • Resource preparation and validation
  • Integration testing setup

Basic Usage

Basic setup

Add startup tasks to your application in Program.cs:

builder.Services.AddStartupTasks(o => o
    .Enabled()
    .StartupDelay("00:00:05"))
    .WithTask<DatabaseSeederTask>(o => o
        .Enabled(builder.Environment.IsDevelopment())
        .StartupDelay("00:00:03"));

var app = builder.Build();
app.Run();

Creating a startup task

Implement the IStartupTask interface:

public sealed class DatabaseSeederTask(
    ILogger<DatabaseSeederTask> logger,
    AppDbContext dbContext) : IStartupTask, IRetryStartupTask, ITimeoutStartupTask
{
    RetryStartupTaskOptions IRetryStartupTask.Options => new()
    {
        Attempts = 3,
        Backoff = TimeSpan.FromSeconds(2)
    };

    TimeoutStartupTaskOptions ITimeoutStartupTask.Options => new()
    {
        Timeout = TimeSpan.FromSeconds(30)
    };

    public async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("Starting database seeding");
        await dbContext.SeedAsync(cancellationToken);
        logger.LogInformation("Database seeding completed");
    }
}

When the host has started and both configured delays have elapsed, the application log contains Database seeding completed. An exception is logged by the service; setting HaltOnFailure() at service or task level terminates the process with Environment.FailFast.

Configuration options

Tasks can be configured with various options:

builder.Services.AddStartupTasks()
    .WithTask<ConfigValidationTask>(o => o
        .Enabled(true)        // Enable/disable the task
        .StartupDelay("00:00:02")  // Add delay before execution
        .Order(1))            // Sort launch order; does not make execution sequential
    .WithTask<CacheWarmupTask>(o => o
        .Enabled(builder.Environment.IsProduction())
        .Order(2));

Adding behaviors

Add behaviors to modify task execution:

builder.Services.AddStartupTasks()
    .WithTask<DatabaseSeederTask>()
    .WithBehavior<RetryStartupTaskBehavior>()
    .WithBehavior<TimeoutStartupTaskBehavior>();

Built-in retry and timeout behaviors act on tasks that implement IRetryStartupTask and ITimeoutStartupTask, respectively. Behaviors are global and wrap every registered startup task; each behavior decides whether it applies to the current task.