Many business operations shouldn't execute during an HTTP request. Tasks such as sending emails, generating reports, synchronizing data with external systems, processing message queues, or cleaning temporary files can take several seconds—or even minutes—to complete. Executing these operations synchronously increases request latency, reduces application throughput, and negatively impacts user experience.
ASP.NET Core provides multiple approaches for running work in the background, each designed for different scenarios. Understanding when to use Hosted Services, Worker Services, and scheduled jobs helps build scalable, responsive, and maintainable applications.
In this article, we'll compare these background processing options, implement production-ready examples, and discuss best practices for choosing the right solution.
Understanding Background Processing
Why Run Tasks in the Background?
Background processing allows applications to perform long-running or recurring work independently of incoming HTTP requests.
Common examples include:
Sending emails
Processing uploaded files
Synchronizing inventory
Generating invoices
Cleaning expired records
Consuming message queues
Scheduled database maintenance
Moving these operations outside the request pipeline improves responsiveness and prevents users from waiting for non-essential tasks to finish.
Hosted Services
What Is a Hosted Service?
A Hosted Service runs alongside an ASP.NET Core application and starts automatically when the application starts.
ASP.NET Core provides the BackgroundService base class for implementing long-running background processes.
public sealed class EmailBackgroundService
: BackgroundService
{
private readonly ILogger<EmailBackgroundService> _logger;
public EmailBackgroundService(
ILogger<EmailBackgroundService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(
CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation(
"Checking email queue...");
await Task.Delay(
TimeSpan.FromSeconds(30),
stoppingToken);
}
}
}
Register the Hosted Service
builder.Services.AddHostedService<EmailBackgroundService>();
Why This Approach?
BackgroundService integrates directly with the ASP.NET Core hosting lifecycle.
The framework automatically starts the service when the application starts and signals it to stop gracefully during shutdown. Using the provided cancellation token ensures ongoing work can complete safely without abrupt termination.
Worker Services
What Is a Worker Service?
A Worker Service is a standalone .NET application designed exclusively for background processing.
Unlike Hosted Services, Worker Services don't expose HTTP endpoints unless you explicitly add them.
Typical use cases include:
Queue consumers
File processors
ETL pipelines
Long-running synchronization services
Scheduled data imports
The implementation closely resembles a Hosted Service because both rely on the Generic Host.
public sealed class InventoryWorker
: BackgroundService
{
protected override async Task ExecuteAsync(
CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine(
"Synchronizing inventory...");
await Task.Delay(
TimeSpan.FromMinutes(5),
stoppingToken);
}
}
}
Why Choose a Worker Service?
Worker Services separate background workloads from web applications, allowing each component to scale independently.
For example, an e-commerce platform can scale its API based on incoming traffic while scaling inventory processors independently based on queue depth.
Scheduled Jobs
Some background work doesn't need to run continuously.
Examples include:
Daily reports
Weekly backups
Monthly billing
Database cleanup
Cache refresh
Instead of continuously polling for work, scheduled jobs execute at predefined intervals.
A common approach is to use a scheduling library that supports recurring tasks, retries, and monitoring.
The scheduling framework becomes responsible for determining when jobs execute, while your application focuses only on implementing the business logic.
Why Use Scheduled Jobs?
Scheduling avoids unnecessary CPU usage caused by constantly checking whether work exists.
It also provides better visibility into execution history, failures, and retry behavior.
End-to-End Implementation
Consider an online retail application.
Architecture:
Customer
│
▼
ASP.NET Core API
│
├────────► Order Database
│
├────────► Background Email Service
│
├────────► Inventory Worker
│
└────────► Scheduled Cleanup Job
Workflow:
A customer places an order.
The API immediately stores the order.
The HTTP response is returned without waiting for email delivery.
A Hosted Service processes the email queue.
A Worker Service synchronizes inventory with warehouse systems.
A scheduled job removes expired shopping carts every night.
Separating these responsibilities improves responsiveness while allowing each background process to evolve independently.
Join the conversation! Your thoughts help the community grow.