Not every task in an ASP.NET Core application should execute during an HTTP request. Operations such as sending emails, generating reports, processing files, or synchronizing data can increase response times and negatively impact the user experience if performed synchronously.
Background processing allows these long-running or non-critical tasks to execute independently of incoming requests. ASP.NET Core offers several approaches, each designed for different workloads and scalability requirements.
In this article, we'll compare the most common background processing options and discuss when to use each one in production applications.
Why Use Background Processing?
Imagine an e-commerce application where placing an order triggers several operations:
If all these tasks execute before returning a response, users experience unnecessary delays.
Instead, the API can immediately acknowledge the order while background workers process the remaining tasks asynchronously.
This improves application responsiveness and allows long-running operations to execute independently.
BackgroundService
BackgroundService is the built-in solution for running long-lived background tasks in ASP.NET Core.
Example:
public class Worker : BackgroundService
{
protected override async Task ExecuteAsync(
CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("Running background task...");
await Task.Delay(5000, stoppingToken);
}
}
}
Register the service:
builder.Services.AddHostedService<Worker>();
Best Use Cases
It is lightweight and requires no external dependencies.
Hangfire
Hangfire is a popular library for persistent background jobs.
Unlike BackgroundService, Hangfire stores jobs in persistent storage such as SQL Server or Redis. Jobs survive application restarts and provide automatic retry capabilities.
Example:
BackgroundJob.Enqueue<IEmailService>(
service => service.SendWelcomeEmail(userId));
Hangfire also provides a dashboard for monitoring:
Running jobs
Failed jobs
Scheduled jobs
Retries
Best Use Cases
Quartz.NET
Quartz.NET is a powerful scheduling library for recurring and calendar-based jobs.
Example scenarios include:
Every day at midnight
Every Monday morning
First day of each month
Every five minutes
Quartz.NET supports advanced scheduling expressions that are difficult to implement using simple timers.
Best Use Cases
Scheduled reports
Database maintenance
Data synchronization
Batch processing
Azure Functions
Azure Functions provide serverless background execution based on events.
Functions can be triggered by:
HTTP requests
Timers
Azure Storage
Azure Service Bus
Event Grid
Blob uploads
Example timer trigger:
[Function("CleanupJob")]
public void Run(
[TimerTrigger("0 */5 * * * *")] TimerInfo timer)
{
Console.WriteLine("Cleanup executed.");
}
Azure automatically scales the execution based on workload.
Best Use Cases
Azure Service Bus
For enterprise applications, asynchronous messaging is often the preferred approach.
Instead of executing work immediately, an API publishes a message.
Client
│
ASP.NET Core API
│
Azure Service Bus
│
Background Worker
The worker processes messages independently.
Benefits include:
Loose coupling
Improved reliability
Automatic retries
Load leveling
Better scalability
This approach is widely used in microservices architectures.
Feature Comparison
| Feature | BackgroundService | Hangfire | Quartz.NET | Azure Functions | Azure Service Bus |
|---|
| Built into ASP.NET Core | ✔ | ✖ | ✖ | ✖ | ✖ |
| Persistent jobs | ✖ | ✔ | ✔ | ✔ | ✔ |
| Scheduling support | Basic | Good | Excellent | Excellent | Event-driven |
| Automatic retries | Limited | ✔ | ✔ | ✔ | ✔ |
| Dashboard | ✖ | ✔ | Limited | Azure Portal | Azure Portal |
| Best for | Simple background tasks | Business jobs | Scheduled jobs | Serverless workloads | Distributed systems |
Choosing the Right Solution
Choose BackgroundService when:
Choose Hangfire when:
Jobs must survive application restarts.
Automatic retries are important.
A monitoring dashboard is needed.
Choose Quartz.NET when:
Choose Azure Functions when:
Building cloud-native applications.
Event-driven execution is preferred.
Automatic scaling is required.
Choose Azure Service Bus when:
Best Practices
Keep HTTP requests short and responsive.
Move long-running work to background services.
Design background jobs to be idempotent so retries don't produce duplicate results.
Use cancellation tokens to support graceful shutdown.
Log failures and monitor job execution.
Choose persistent job processing for critical business operations.
Use message queues for distributed applications instead of direct service calls.
Common Mistakes
Running Long Tasks Inside Controllers
Controllers should return responses quickly. Executing lengthy operations during a request increases response time and reduces scalability.
Using BackgroundService for Critical Jobs
BackgroundService stores work in memory. If the application restarts unexpectedly, in-progress work may be lost. For critical workloads, prefer persistent job schedulers or message queues.
Ignoring Retry Logic
Network failures and temporary database issues are common. Background jobs should support retries with appropriate error handling.
Choosing a Complex Solution Too Early
Not every application requires Hangfire or Azure Service Bus. For simple periodic tasks, BackgroundService may be sufficient and easier to maintain.
Conclusion
Background processing is essential for building responsive and scalable ASP.NET Core applications. By moving long-running tasks outside the request pipeline, applications can improve user experience while handling work more efficiently.
The right solution depends on your requirements. BackgroundService is ideal for lightweight background tasks, Hangfire provides reliable job processing with persistence, Quartz.NET excels at advanced scheduling, Azure Functions enable serverless execution, and Azure Service Bus supports scalable, event-driven architectures.
Rather than choosing the most feature-rich option, select the approach that matches your application's complexity, reliability requirements, and deployment environment. A well-designed background processing strategy improves performance, enhances scalability, and makes your .NET applications easier to maintain as they grow.