What is a hosted service / BackgroundService in ASP.NET Core?

ASP.NET Core Hosting Intermediate 2–3 Yrs3–5 Yrs Conceptual 1 min read

Quick Answer

IHostedService (usually via the BackgroundService base class) runs background work tied to the app's lifetime — queue processing, scheduled jobs, cache warming. The host starts it on startup and signals a CancellationToken on shutdown so it can stop gracefully.

A hosted service runs background work for the lifetime of the app — processing a queue, a periodic job, warming a cache. I derive from BackgroundService, implement ExecuteAsync with a loop and a Task.Delay, and register it with AddHostedService. The host cancels the token on shutdown so I can stop cleanly. The mistake people make is injecting DbContext into the constructor — the hosted service is a singleton so that's a captive dependency. I inject IServiceScopeFactory and create a fresh scope inside each iteration to resolve scoped services.

  • Implement ExecuteAsync(CancellationToken) — typically a loop that does work then await Task.Delay(interval, token).
  • Register with services.AddHostedService<MyWorker>().
  • The key gotcha: a hosted service is a singleton, so you cannot inject a scoped service (like DbContext) into its constructor — that's a captive dependency. Inject IServiceScopeFactory and create a scope for each iteration.
  • Long blocking work in ExecuteAsync before the first await delays app startup — get to an await quickly.
  • For real scheduling, use Quartz.NET or Hangfire rather than hand-rolled timers.

  • Injecting a scoped service (DbContext, a repository) into the hosted service constructor — it's a singleton, so this is a captive dependency.
  • Doing heavy synchronous work before the first await in ExecuteAsync, which blocks application startup.
  • Not honouring the CancellationToken, so the app hangs on shutdown.
  • Letting an unhandled exception escape ExecuteAsync — it stops the service silently (pre-.NET 6) or the host (later, by default).