ASP.NET Core  

Background Tasks with Hosted Services in ASP.NET Core: A Complete Guide

In modern web applications, some operations don’t need to be executed immediately within a user request. Examples include:

  • Sending emails or notifications

  • Cleaning up expired data

  • Generating reports

  • Processing queues or background jobs

ASP.NET Core provides a built-in solution for these scenarios: Hosted Services. Hosted services allow you to run background tasks independently of HTTP requests in a clean, reliable way.

This article explains how to implement background tasks using hosted services, with practical examples for full-stack applications.

What You Will Learn

  • What hosted services are and why they are useful

  • Types of hosted services in ASP.NET Core

  • Implementing IHostedService for custom background tasks

  • Using BackgroundService for recurring jobs

  • Scheduling tasks and cancellation handling

  • Best practices for production-ready background processing

Part 1: Understanding Hosted Services

A hosted service is a class that runs in the background of your ASP.NET Core application. It starts when the application starts and stops when the application shuts down.

Types:

  1. IHostedService: Basic interface for background services.

  2. BackgroundService: Abstract class implementing IHostedService with a convenient ExecuteAsync method for long-running tasks.

Part 2. Creating a simple hosted service

Step 1: Create a Class Implementing BackgroundService

Services/EmailSenderService.cs

using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

public class EmailSenderService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Replace with your background logic
            Console.WriteLine($"Sending emails at {DateTime.Now}");

            // Wait for 1 minute before running again
            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }
}

Step 2: Register Hosted Service

In Program.cs:

builder.Services.AddHostedService<EmailSenderService>();

Now, when your application starts, EmailSenderService runs in the background automatically.

Part 3. Using Dependency Injection in hosted services

Hosted services can use services like DbContext, email service, or logging via dependency injection.

public class CleanupService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<CleanupService> _logger;

    public CleanupService(IServiceScopeFactory scopeFactory, ILogger<CleanupService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = _scopeFactory.CreateScope();
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            var expiredItems = db.Tokens.Where(t => t.Expiry < DateTime.Now);
            db.Tokens.RemoveRange(expiredItems);
            await db.SaveChangesAsync();

            _logger.LogInformation("Expired tokens cleaned at {time}", DateTime.Now);
            await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
        }
    }
}

Here, IServiceScopeFactory ensures proper lifetime management of scoped services like DbContext.

Part 4. Scheduling Background Tasks

For recurring tasks, you can use Task.Delay inside ExecuteAsync. For more advanced scheduling, consider:

  • Cron jobs with Cronos or Hangfire

  • Timer-based execution

Example using a Timer:

public class TimerService : IHostedService, IDisposable
{
    private Timer _timer;

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(30));
        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        Console.WriteLine($"Task executed at {DateTime.Now}");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    public void Dispose() => _timer?.Dispose();
}

Part 5. Integrating with email Service (Real-world Example)

Suppose you want to send queued emails in the background:

public class QueuedEmailService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;

    public QueuedEmailService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = _scopeFactory.CreateScope();
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var emailService = scope.ServiceProvider.GetRequiredService<EmailService>();

            var pendingEmails = db.EmailQueue.Where(e => !e.Sent);
            foreach (var email in pendingEmails)
            {
                await emailService.SendEmailAsync(email);
                email.Sent = true;
            }

            await db.SaveChangesAsync();
            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }
}

This pattern ensures emails are sent without blocking user requests.

Part 6. Stopping Background Taska

CancellationToken stoppingToken is automatically triggered when:

  • The application shuts down

  • The service is removed or restarted

Always check the token in long-running loops:

while (!stoppingToken.IsCancellationRequested)
{
    // Your logic
}

Part 7. Best Practices

  1. Use IServiceScopeFactory for scoped services (like DbContext)

  2. Handle exceptions – log errors without crashing the service

  3. Avoid blocking calls – always use async/await

  4. Use Task.Delay instead of Thread.Sleep

  5. Keep tasks lightweight – offload heavy processing to external queues if needed

  6. Graceful shutdown – always respect stoppingToken

Part 8. Monitoring and Scaling

  • Use application logs to monitor background tasks

  • Combine with health checks to ensure background services are running

  • For heavy workloads, consider queue-based processing with Hangfire, RabbitMQ, or Azure Functions

  • Hosted services scale automatically with the application instance, so tasks run per server instance

Conclusion

Hosted services in ASP.NET Core allow you to run background tasks safely and efficiently. Key takeaways:

  • BackgroundService and IHostedService are the foundation

  • Proper dependency injection and cancellation handling is crucial

  • Can be used for emails, cleanup tasks, reporting, or queue processing

  • Combine with health checks and logging for production-ready applications

By implementing background tasks with hosted services, you can improve performance, user experience, and reliability of your full-stack applications.