Many business operations shouldn't run during an HTTP request. Sending emails, generating reports, processing invoices, synchronizing data, cleaning temporary files, and executing scheduled tasks are better handled in the background to improve application responsiveness and reliability.
ASP.NET Core provides multiple options for implementing background jobs, but choosing the right one depends on your application's requirements. While BackgroundService is ideal for lightweight workers, Hangfire and Quartz.NET offer advanced scheduling, retries, persistence, and monitoring capabilities.
Rather than comparing features at a high level, this article explains when to use each approach, how they differ, and which solution best fits common production scenarios.
Note: There is no single "best" background job framework. The right choice depends on whether you need simple background processing, recurring schedules, or enterprise-grade job orchestration.
Why Use Background Jobs?
Running long-running operations inside an API request can lead to:
Moving these operations to background workers allows the API to respond immediately while the work continues independently.
Common Background Job Scenarios
Background processing is commonly used for:
These tasks typically don't require an immediate response to the user.
Choosing the Right Solution
The following comparison summarizes the strengths of each option.
| Feature | Hosted Service | Hangfire | Quartz.NET |
|---|
| Built into ASP.NET Core | ✅ | ❌ | ❌ |
| Cron scheduling | Limited | ✅ | ✅ |
| Dashboard | ❌ | ✅ | ❌ |
| Persistent jobs | ❌ | ✅ | ✅ |
| Automatic retries | ❌ | ✅ | Configurable |
| Distributed execution | ❌ | Limited | ✅ |
| Learning curve | Low | Medium | High |
Each solution targets a different level of application complexity.
Option 1 – Hosted Services
Hosted Services are the simplest way to execute background work in ASP.NET Core.
Create a worker by inheriting from BackgroundService.
public class CleanupWorker : BackgroundService
{
protected override async Task ExecuteAsync(
CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("Cleaning files...");
await Task.Delay(
TimeSpan.FromHours(1),
stoppingToken);
}
}
}
Register the service:
builder.Services.AddHostedService<CleanupWorker>();
Hosted Services work well for lightweight, continuously running tasks.
Option 2 – Hangfire
Hangfire simplifies background processing by storing jobs in persistent storage such as SQL Server or Redis.
Install Hangfire:
dotnet add package Hangfire.AspNetCore
dotnet add package Hangfire.SqlServer
Configure Hangfire:
builder.Services.AddHangfire(config =>
config.UseSqlServerStorage(connectionString));
builder.Services.AddHangfireServer();
Create a background job:
BackgroundJob.Enqueue(() =>
EmailService.SendWelcomeEmail());
Create a recurring job:
RecurringJob.AddOrUpdate(
"daily-report",
() => ReportService.Generate(),
Cron.Daily);
Hangfire automatically retries failed jobs and provides a built-in web dashboard for monitoring execution.
Option 3 – Quartz.NET
Quartz.NET is designed for advanced scheduling scenarios.
Install Quartz:
dotnet add package Quartz
Create a scheduled job:
public class ReportJob : IJob
{
public Task Execute(
IJobExecutionContext context)
{
Console.WriteLine("Generating report...");
return Task.CompletedTask;
}
}
Quartz supports:
Cron expressions
Calendars
Job priorities
Misfire handling
Complex scheduling rules
Clustered execution
It is commonly used in enterprise systems with sophisticated scheduling requirements.
Feature Comparison
Different projects require different capabilities.
| Scenario | Recommended Option |
|---|
| Simple background loop | Hosted Service |
| Email processing | Hangfire |
| Scheduled reports | Hangfire |
| Enterprise scheduling | Quartz.NET |
| Cron-based workflows | Quartz.NET |
| Lightweight worker | Hosted Service |
| Retry support | Hangfire |
| Complex recurring schedules | Quartz.NET |
Choosing the simplest solution that meets your requirements often results in easier maintenance.
Architecture Overview
flowchart LR
A[ASP.NET Core API]
B[Background Job]
C[(Database)]
D[Email Service]
E[Scheduler]
A --> B
B --> C
B --> D
E --> B
The API delegates long-running work to background processing while remaining responsive to incoming requests.
Performance Considerations
Keep these recommendations in mind:
Avoid CPU-intensive work inside request pipelines.
Use cancellation tokens where appropriate.
Configure retry policies carefully.
Prevent duplicate job execution.
Monitor job duration and failure rates.
Limit concurrent workers to avoid resource exhaustion.
Proper configuration improves both throughput and reliability.
Common Production Mistakes
| Problem | Root Cause |
|---|
| Duplicate jobs | Missing idempotency |
| Memory leaks | Long-running tasks holding resources |
| Lost jobs | In-memory scheduling without persistence |
| Thread starvation | Blocking asynchronous code |
| Failed retries | Incorrect retry configuration |
| Overloaded workers | Unlimited concurrency |
Most production issues stem from poor job management rather than the framework itself.
Best Practices
Keep background jobs small and focused.
Store important jobs in persistent storage.
Log every failed execution.
Configure retry limits appropriately.
Monitor execution time and queue length.
Use dependency injection for job services.
Design jobs to be idempotent whenever possible.
Common Anti-Patterns
Avoid these implementation mistakes:
Running long operations directly inside controllers.
Creating fire-and-forget tasks using Task.Run().
Ignoring cancellation tokens.
Scheduling duplicate recurring jobs.
Performing heavy synchronous work inside background workers.
Assuming background jobs always complete successfully.
FAQ
Should I always use Hangfire instead of Hosted Services?
No. Hosted Services are sufficient for lightweight, continuously running tasks. Hangfire is more suitable when persistence, retries, and scheduling are required.
Is Quartz.NET better than Hangfire?
Not necessarily. Quartz.NET excels at advanced scheduling, while Hangfire focuses on developer productivity and job management.
Can Hosted Services process queues?
Yes. Hosted Services are commonly used to consume Azure Service Bus, RabbitMQ, or Kafka messages.
Can multiple Hangfire servers process the same jobs?
Yes. Hangfire supports distributed processing when configured with shared storage.
Conclusion
Background processing is essential for building responsive and scalable ASP.NET Core applications. Rather than executing long-running operations during HTTP requests, background jobs allow work to be processed asynchronously while improving user experience and application reliability.
Hosted Services, Hangfire, and Quartz.NET each solve different problems. By understanding their strengths, limitations, and ideal use cases, you can choose the right solution for your workload and build background processing systems that remain reliable, maintainable, and production-ready.