Isaac.

Background Services in ASP.NET Core

Implement long-running background services.

By EMEPublished: February 20, 2025
asp.net corebackground serviceshosted servicesworker services

A Simple Analogy

Background services are like janitors working after hours. They run silently, performing maintenance while the main application serves users.


Why Background Services?

  • Async work: Don't block user requests
  • Scheduled tasks: Run at intervals
  • Polling: Monitor for changes
  • Cleanup: Maintenance operations
  • Reliability: Integrated with host lifecycle

Hosted Service

public class DataSyncService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<DataSyncService> _logger;
    
    public DataSyncService(IServiceProvider serviceProvider, ILogger<DataSyncService> logger)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                _logger.LogInformation("Starting data sync");
                
                using var scope = _serviceProvider.CreateScope();
                var service = scope.ServiceProvider.GetRequiredService<IDataService>();
                await service.SyncAsync();
                
                _logger.LogInformation("Data sync completed");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Data sync failed");
            }
            
            // Run every 5 minutes
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }
    
    public override Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("DataSyncService is stopping");
        return base.StopAsync(cancellationToken);
    }
}

// Register
builder.Services.AddHostedService<DataSyncService>();

Windows Service

// Program.cs
builder.Host
    .UseWindowsService(options => options.ServiceName = "MyAppService");

// Install
sc create MyAppService binPath= "C:\path\to\app.exe"

// Start
sc start MyAppService

Timer-Based Service

public class TimedCleanupService : IHostedService
{
    private readonly ILogger<TimedCleanupService> _logger;
    private Timer _timer;
    
    public TimedCleanupService(ILogger<TimedCleanupService> logger)
    {
        _logger = logger;
    }
    
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Cleanup service started");
        
        _timer = new Timer(DoCleanup, null, TimeSpan.Zero, TimeSpan.FromHours(1));
        
        return Task.CompletedTask;
    }
    
    private void DoCleanup(object state)
    {
        _logger.LogInformation("Running cleanup");
        // Cleanup logic here
    }
    
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _timer?.Dispose();
        return Task.CompletedTask;
    }
}

Scoped Service Access

public class NotificationService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                // Get scoped services
                var emailService = scope.ServiceProvider
                    .GetRequiredService<IEmailService>();
                var context = scope.ServiceProvider
                    .GetRequiredService<AppDbContext>();
                
                var notifications = await context.Notifications
                    .Where(n => !n.Sent)
                    .ToListAsync(stoppingToken);
                
                foreach (var notification in notifications)
                {
                    await emailService.SendAsync(notification);
                    notification.Sent = true;
                }
                
                await context.SaveChangesAsync(stoppingToken);
            }
            
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }
}

Best Practices

  1. Check cancellation: Respect StoppingToken
  2. Use scope for DB: Create scope per iteration
  3. Handle exceptions: Don't let service crash
  4. Log activity: Track what's happening
  5. Graceful shutdown: Clean up resources

Related Concepts

  • Worker services
  • Hangfire background jobs
  • Windows services
  • System timers

Summary

Implement background services for long-running async operations. Use BackgroundService or IHostedService to integrate with application lifecycle.