.NET Core  

Building Background Tasks in ASP.NET Core

Introduction

In modern ASP.NET Core applications, especially those involving microservices, background processing is a key requirement. Whether it’s sending emails, processing Kafka messages, running cron-like jobs, or handling long-running workflows, background services are essential.

With the introduction of the Generic Host and robust improvements in .NET 6, 7, and 8, ASP.NET Core now provides powerful patterns through,

  • IHostedService
  • BackgroundService
  • Worker Service templates

This article dives deep into how to design, build, and optimize background services for production.

Worker Services vs Hosted Services: What’s the Difference?

Feature IHostedService BackgroundService Worker Service Template
Interface-based Yes Yes (abstracts IHostedService) Yes (project template-based)
Long-running support Needs manual implementation Provides an infinite loop (ExecuteAsync) Yes
Best for Short-lived or event-based tasks Long-running background jobs Standalone background apps

Setting Up a Worker Service Project

You can create a worker service using.

dotnet new worker -n EmailBackgroundService

This creates a ready-to-run worker template using the Generic Host.

The Program.cs in .NET 8+ is minimal.

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.AddHostedService<EmailSenderWorker>();
    })
    .Build();

await host.RunAsync();

Creating a Custom BackgroundService

Here’s a simple example of a background worker.

public class EmailSenderWorker : BackgroundService
{
    private readonly ILogger<EmailSenderWorker> _logger;

    public EmailSenderWorker(ILogger<EmailSenderWorker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Sending queued emails at: {time}", DateTimeOffset.Now);
            await SendPendingEmails();
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }

    private Task SendPendingEmails()
    {
        // Implement actual email logic here
        return Task.CompletedTask;
    }
}

Advanced Hosted Services with IHostedService

When you need fine-grained control over start/stop behavior (e.g., for message queue consumers).

public class KafkaConsumerService : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        // Connect to Kafka and begin consuming
        return Task.Run(() => ConsumeMessages(cancellationToken), cancellationToken);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        // Cleanup, disconnect
        return Task.CompletedTask;
    }

    private void ConsumeMessages(CancellationToken cancellationToken)
    {
        // Poll Kafka and process messages
    }
}

Managing Multiple Background Services

You can register multiple services easily.

services.AddHostedService<EmailSenderWorker>();
services.AddHostedService<KafkaConsumerService>();
services.AddHostedService<DataSyncWorker>();

Each runs independently inside the host.

Handling Exceptions & Graceful Shutdown

To avoid crashing the entire app due to unhandled exceptions in workers.

try
{
    await SomeBackgroundTask();
}
catch (Exception ex)
{
    _logger.LogError(ex, "An error occurred.");
}

For graceful shutdown, ensure your workers respond to the CancellationToken from ExecuteAsync.

Dependency Injection in Background Services

Services are injected normally.

public class DataSyncWorker : BackgroundService
{
    private readonly IDataService _dataService;

    public DataSyncWorker(IDataService dataService)
    {
        _dataService = dataService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _dataService.SyncExternalData();
    }
}

Scheduling and Cron-like Jobs

For scheduled tasks, consider,

  • Quartz.NET for CRON jobs.
  • Hangfire for background jobs with retry support.
  • Or a Timer-based service with Task.Delay.

Example using the Cronos package.

var cron = CronExpression.Parse("0 */5 * * * *");
var next = cron.GetNextOccurrence(DateTimeOffset.Now);

Logging, Metrics & Observability

Integrate with,

  • Serilog / Seq for structured logging
  • Prometheus for metrics
  • OpenTelemetry for traces and spans

Production Tips

  • Always handle exceptions gracefully
  • Use scoped services inside BackgroundService via IServiceScopeFactory
  • Monitor using health checks (IHealthCheck)
  • Separate retry logic (e.g., use Polly)
  • Set max concurrency (e.g., in queues) to prevent overload

Conclusion

ASP.NET Core’s Hosted Services and Worker Services provide powerful primitives for executing background tasks in a clean, reliable, and scalable way.

Whether you're building message consumers, email dispatchers, or data processors, background services allow you to decouple, scale, and manage your application’s background logic like a pro.

Happy coding!