Hosted Service là gì và khi nào cần sử dụng?
IHostedService và BackgroundService 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?
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
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 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();
}
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>();
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;
}
}
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();
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();
dotnet new worker -n MyWorker
// 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);
}
}
}
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();
// ✅ 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);
}
}
// ✅ 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);
}
}
// ✅ 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);
}
}
}