🔄 Key Takeaways: Run background tasks, scheduled jobs, and long-running operations with IHostedService and BackgroundService.
Your app needs to do things in the background. Background Services are the answer.
🚀 Create Background Service
// ✅ Simple background service
public class SampleBackgroundService : BackgroundService
{
private readonly ILogger<SampleBackgroundService> _logger;
public SampleBackgroundService(ILogger<SampleBackgroundService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Background service started");
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Background service running at {Time}", DateTime.UtcNow);
try
{
await DoWorkAsync(stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in background service");
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
_logger.LogInformation("Background service stopped");
}
}
// Register
builder.Services.AddHostedService<SampleBackgroundService>();
💡 Complete: Scheduled Job
// ✅ Scheduled job with cron-like scheduling
public class ScheduledJobService : BackgroundService
{
private readonly IServiceProvider _services;
private readonly ILogger<ScheduledJobService> _logger;
private readonly TimeSpan _interval = TimeSpan.FromHours(24);
private DateTime _nextRun = DateTime.UtcNow;
public ScheduledJobService(IServiceProvider services, ILogger<ScheduledJobService> logger)
{
_services = services;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Scheduled job service started");
while (!stoppingToken.IsCancellationRequested)
{
if (DateTime.UtcNow >= _nextRun)
{
try
{
await RunJobAsync(stoppingToken);
_nextRun = DateTime.UtcNow.Add(_interval);
}
catch (Exception ex)
{
_logger.LogError(ex, "Scheduled job failed");
}
}
await Task.Delay(TimeSpan.FromSeconds(60), stoppingToken);
}
}
private async Task RunJobAsync(CancellationToken cancellationToken)
{
using var scope = _services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var emailService = scope.ServiceProvider.GetRequiredService<IEmailService>();
_logger.LogInformation("Running daily scheduled job");
// ✅ Database cleanup
var oldRecords = await dbContext.Logs
.Where(l => l.CreatedAt < DateTime.UtcNow.AddDays(-30))
.ToListAsync(cancellationToken);
dbContext.Logs.RemoveRange(oldRecords);
await dbContext.SaveChangesAsync(cancellationToken);
// ✅ Send report
await emailService.SendReportAsync();
_logger.LogInformation("Scheduled job completed");
}
}
💡 Pro Tip: Graceful Shutdown
// ✅ Proper shutdown handling
public class RobustBackgroundService : BackgroundService
{
private readonly ILogger _logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Service starting");
try
{
await RunAsync(stoppingToken);
}
catch (OperationCanceledException)
{
_logger.LogInformation("Service stopped gracefully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Service crashed");
throw;
}
}
private async Task RunAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await ProcessItemAsync(stoppingToken);
try
{
await Task.Delay(1000, stoppingToken);
}
catch (OperationCanceledException)
{
break;
}
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service is stopping...");
// ✅ Cleanup resources
await CleanupAsync();
await base.StopAsync(cancellationToken);
_logger.LogInformation("Service stopped");
}
}
