Lesson 19 of 40
Architecture
Intermediate
35 min
Background Services & Workers
Build reliable background processing with IHostedService, BackgroundService, queued tasks, and Azure Service Bus integration.
Part 1: BackgroundService Base Class
public class EmailQueueWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var email = await _queue.DequeueAsync(stoppingToken);
await _sender.SendAsync(email);
}
}
}
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var email = await _queue.DequeueAsync(stoppingToken);
await _sender.SendAsync(email);
}
}
}
Part 2: Periodic Timer Pattern
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
while (await timer.WaitForNextTickAsync(ct))
await DoWorkAsync(ct);
}
{
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
while (await timer.WaitForNextTickAsync(ct))
await DoWorkAsync(ct);
}
PeriodicTimer is garbage-free and handles drift correctly.Part 3: Scoped Services in Workers
BackgroundService is a singleton. To use scoped services like DbContext:
using var scope = _services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Orders.CountAsync();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Orders.CountAsync();
Part 4: Outbox Pattern for Reliability
Prevent message loss on failure using the Transactional Outbox Pattern:
- Write domain event to outbox table in same DB transaction
- BackgroundService reads outbox and publishes messages
- Mark as sent only after successful delivery