100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Background Services with IHostedService

How to run long-lived and scheduled background work in ASP.NET Core using IHostedService and the BackgroundService base class.

Data & ServicesIntermediate9 min readJul 10, 2026
Analogies

IHostedService and BackgroundService

IHostedService is the core abstraction ASP.NET Core's generic host uses to run background work alongside the web server: it defines StartAsync(CancellationToken) and StopAsync(CancellationToken), which the host invokes when the application starts up and shuts down respectively. Rather than implementing IHostedService directly, most real-world background workers derive from the abstract BackgroundService class, which implements IHostedService for you and exposes a single ExecuteAsync(CancellationToken stoppingToken) method containing your actual work loop — typically a while (!stoppingToken.IsCancellationRequested) loop with an await Task.Delay between iterations.

🏏

Cricket analogy: IHostedService is like the ground staff crew who prepare the pitch before play starts and cover it after stumps — StartAsync is rolling the pitch before the first ball, StopAsync is covering it once the day's play ends, running independently of the actual match on the field.

Background services are registered with builder.Services.AddHostedService<T>() where T derives from BackgroundService, and they participate in the same DI container as everything else, but because a BackgroundService itself typically has an implicit Singleton-like lifetime bound to the host, it cannot directly inject Scoped services such as a repository or DbContext through its constructor — doing so causes the captive dependency problem described in the service-lifetimes topic. The standard fix is injecting IServiceScopeFactory and creating a new scope inside each loop iteration, ensuring every unit of background work gets a fresh, properly-scoped DbContext just like a real HTTP request would.

🏏

Cricket analogy: This mirrors a groundskeeper (background service) needing a fresh set of pitch-preparation tools (Scoped dependency) checked out for every single match day rather than keeping one permanently borrowed set that goes stale — IServiceScopeFactory is the equipment room issuing a fresh checkout each time.

Scheduling and Graceful Shutdown

A common pattern is a periodic worker that runs a task every N minutes using PeriodicTimer (preferred over Task.Delay in a loop since .NET 6, because it avoids drift and integrates cleanly with cancellation), while more complex scheduling needs — like cron-style expressions — are typically handled by a library such as Quartz.NET or Hangfire rather than hand-rolled timer logic. Graceful shutdown matters because the host gives running IHostedService instances a limited shutdown timeout (configurable via HostOptions.ShutdownTimeout, five seconds by default) to finish StopAsync before the process is forcibly terminated, so long-running loops must check stoppingToken.IsCancellationRequested frequently and avoid work that can't be interrupted within that window.

🏏

Cricket analogy: A PeriodicTimer worker is like a scheduled drinks break called at fixed intervals during a match rather than an umpire eyeballing the clock loosely — it's a disciplined, drift-free schedule; graceful shutdown is like the umpire giving fielders a firm but bounded window to leave the field once stumps are called.

csharp
public class EnrollmentDigestWorker : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<EnrollmentDigestWorker> _logger;

    public EnrollmentDigestWorker(IServiceScopeFactory scopeFactory, ILogger<EnrollmentDigestWorker> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(15));

        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            try
            {
                using var scope = _scopeFactory.CreateScope();
                var repo = scope.ServiceProvider.GetRequiredService<ICourseRepository>();
                var mailer = scope.ServiceProvider.GetRequiredService<IEmailSender>();

                var newEnrollments = await repo.GetRecentEnrollmentsAsync(stoppingToken);
                if (newEnrollments.Count > 0)
                    await mailer.SendDigestAsync(newEnrollments, stoppingToken);
            }
            catch (Exception ex) when (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogError(ex, "Enrollment digest run failed");
            }
        }
    }
}

// Program.cs
builder.Services.AddHostedService<EnrollmentDigestWorker>();

A BackgroundService whose ExecuteAsync throws an unhandled exception stops permanently and, depending on HostOptions.BackgroundServiceExceptionBehavior, can even crash the entire host process. Always wrap the loop body in a try/catch that logs and continues, and reserve unhandled exceptions only for truly fatal, unrecoverable startup failures.

  • IHostedService defines StartAsync/StopAsync; BackgroundService is the common base class exposing ExecuteAsync for the work loop.
  • Register background workers with AddHostedService<T>(), which wires them into the generic host's start/stop lifecycle.
  • A BackgroundService cannot safely inject Scoped services directly; use IServiceScopeFactory.CreateScope() per work unit instead.
  • PeriodicTimer is preferred over a raw Task.Delay loop since .NET 6 because it avoids timing drift and integrates with cancellation.
  • Complex cron-style scheduling is usually delegated to a library like Quartz.NET or Hangfire rather than hand-rolled.
  • HostOptions.ShutdownTimeout bounds how long StopAsync has to finish before the process is forcibly terminated (5 seconds by default).
  • Catch and log exceptions inside the work loop so one failed iteration doesn't silently stop the worker or crash the host.

Practice what you learned

Was this page helpful?

Topics covered

#ASPNETCoreStudyNotes#MicrosoftTechnologies#BackgroundServicesWithIHostedService#Background#Services#IHostedService#BackgroundService#StudyNotes#SkillVeris#ExamPrep