What is a hosted service / BackgroundService in ASP.NET Core?
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 thenawait 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. InjectIServiceScopeFactoryand create a scope for each iteration. - Long blocking work in
ExecuteAsyncbefore the firstawaitdelays app startup — get to anawaitquickly. - 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
awaitinExecuteAsync, 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).