Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hosted Services & Background Tasks

Overview Questions

  • Hosted Service là gì và khi nào cần sử dụng?
  • IHostedServiceBackgroundService khác nhau như thế nào?
  • Làm sao để thực thi task theo schedule?
  • Quản lý cancellation token trong background tasks ra sao?
  • Worker Service là gì và cách tạo?

IHostedService

Interface

public interface IHostedService
{
    Task StartAsync(CancellationToken cancellationToken);
    Task StopAsync(CancellationToken cancellationToken);
}

Implementation

public class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger<TimedHostedService> _logger;
    private Timer? _timer;

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

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");
        
        // Chạy mỗi 1 phút
        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
        
        return Task.CompletedTask;
    }

    private void DoWork(object? state)
    {
        _logger.LogInformation("Timed Hosted Service is working.");
        // Thực thi công việc background
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");
        
        _timer?.Change(Timeout.Infinite, 0);
        
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

// Đăng ký
builder.Services.AddHostedService<TimedHostedService>();

BackgroundService

Abstract Class

// BackgroundService là implementation base của IHostedService
// Cung cấp ExecuteAsync method dễ sử dụng hơn
public abstract class BackgroundService : IHostedService, IDisposable
{
    protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
    
    public virtual Task StartAsync(CancellationToken cancellationToken);
    public virtual Task StopAsync(CancellationToken cancellationToken);
    public virtual void Dispose();
}

Implementation

public class QueueProcessorService : BackgroundService
{
    private readonly ILogger<QueueProcessorService> _logger;
    private readonly Channel<string> _channel;

    public QueueProcessorService(
        ILogger<QueueProcessorService> logger,
        Channel<string> channel)
    {
        _logger = logger;
        _channel = channel;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queue Processor Service starting");

        await foreach (var item in _channel.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                _logger.LogInformation("Processing: {Item}", item);
                await ProcessItemAsync(item, stoppingToken);
            }
            catch (Exception ex) when (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogError(ex, "Error processing item: {Item}", item);
            }
        }

        _logger.LogInformation("Queue Processor Service stopping");
    }

    private async Task ProcessItemAsync(string item, CancellationToken token)
    {
        // Simulate processing
        await Task.Delay(100, token);
    }
}

// Đăng ký
builder.Services.AddSingleton(Channel.CreateUnbounded<string>());
builder.Services.AddHostedService<QueueProcessorService>();

Periodic Background Service

Pattern với Timer

public class PeriodicService : BackgroundService
{
    private readonly ILogger<PeriodicService> _logger;
    private readonly TimeSpan _period = TimeSpan.FromSeconds(30);

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

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                _logger.LogInformation("Doing periodic work at {Time}", DateTimeOffset.UtcNow);
                await DoWorkAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in periodic work");
            }

            await Task.Delay(_period, stoppingToken);
        }
    }

    private async Task DoWorkAsync(CancellationToken token)
    {
        // Thực thi công việc
        await Task.CompletedTask;
    }
}

Scheduled Tasks

Cron-based Scheduler

dotnet add package Coravel
// Program.cs
builder.Services.AddScheduler();

// Tạo scheduled task
public class CleanupTask : IInvocable
{
    private readonly ILogger<CleanupTask> _logger;

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

    public async Task Invoke()
    {
        _logger.LogInformation("Running cleanup task");
        // Cleanup logic
    }
}

// Đăng ký schedule
builder.Services.AddTransient<CleanupTask>();

// Trong Program.cs
var app = builder.Build();
app.Services.UseScheduler(scheduler =>
{
    scheduler.Schedule<CleanupTask>()
        .EveryFiveMinutes()
        .PreventOverlapping();
});

app.Run();

Hangfire

dotnet add package Hangfire
// Program.cs
builder.Services.AddHangfire(config =>
    config.UseSqlServerStorage(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddHangfireServer();

var app = builder.Build();
app.UseHangfireDashboard();

// Recurring job
RecurringJob.AddOrUpdate<ICleanupService>(
    "cleanup",
    service => service.Cleanup(),
    Cron.Daily);

app.Run();

Worker Service Template

Tạo Worker Service

dotnet new worker -n MyWorker

Structure

// Program.cs
using MyWorker;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

var host = builder.Build();
host.Run();

// Worker.cs
namespace MyWorker;

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

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

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken);
        }
    }
}

Run as Windows Service

dotnet add package Microsoft.Extensions.Hosting.WindowsServices
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

// Add Windows Service support
builder.Services.AddWindowsService(options =>
{
    options.ServiceName = "My Worker Service";
});

var host = builder.Build();
host.Run();

Best Practices

1. Handle Cancellation Properly

// ✅ Good
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        await DoWorkAsync(stoppingToken);
        await Task.Delay(1000, stoppingToken);
    }
}

// ❌ Bad - ignore cancellation
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (true) // Infinite loop, no cancellation check
    {
        await DoWorkAsync(CancellationToken.None);
    }
}

2. Handle Exceptions

// ✅ Good - catch và log exceptions
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        try
        {
            await DoWorkAsync(stoppingToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in background service");
        }
        await Task.Delay(1000, stoppingToken);
    }
}

3. Use Scoped Services Correctly

// ✅ Good - tạo scope cho scoped services
public class ScopedWorker : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<ScopedWorker> _logger;

    public ScopedWorker(IServiceProvider serviceProvider, ILogger<ScopedWorker> logger)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Tạo scope mới cho mỗi iteration
            using var scope = _serviceProvider.CreateScope();
            var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            
            await ProcessAsync(dbContext, stoppingToken);
            
            await Task.Delay(5000, stoppingToken);
        }
    }
}