并非应用程序中的所有工作都应在HTTP请求期间完成。
长时间运行的任务、计划作业、重试队列、事件处理和定期维护更适合在后台处理。
在.NET中,后台处理可以通过以下方式实现:
本指南将重点介绍如何使用.NET内置功能构建健壮的后台服务——无需外部框架。
1. IHostedService 和 BackgroundService ASP.NET Core 为后台工作提供了一个清晰的抽象接口:IHostedService。更简单且首选的方式是继承 BackgroundService。
示例:基础后台工作者
public class FileCleanupService : BackgroundService
{
private readonly ILogger<FileCleanupService> _logger;
public FileCleanupService(ILogger<FileCleanupService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Cleaning up files at: {time}", DateTimeOffset.Now);
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
注册服务
builder.Services.AddHostedService<FileCleanupService>();
2. 基于队列的后台任务 在实际场景中,通常希望将工作从控制器推送到队列中,并由后台服务处理。
步骤1:创建后台队列
public interface IBackgroundTaskQueue
{
void Enqueue(Func<CancellationToken, Task> work);
Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken token);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue =
Channel.CreateUnbounded<Func<CancellationToken, Task>>();
public void Enqueue(Func<CancellationToken, Task> work)
{
_queue.Writer.TryWrite(work);
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken token)
{
return await _queue.Reader.ReadAsync(token);
}
}
步骤2:处理任务的后台服务
public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
private readonly IBackgroundTaskQueue _taskQueue;
public QueuedHostedService(ILogger<QueuedHostedService> logger, IBackgroundTaskQueue queue)
{
_logger = logger;
_taskQueue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var task = await _taskQueue.DequeueAsync(stoppingToken);
try
{
await task(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error executing background task");
}
}
}
}
步骤3:在控制器中注册和使用
builder.Services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
builder.Services.AddHostedService<QueuedHostedService>();
// 控制器使用示例:
_taskQueue.Enqueue(async token =>
{
await ProcessOrderAsync(orderId, token);
});
3. 定时作业 简单的重复性作业也可以使用 PeriodicTimer(在.NET 6+中):
public class TimedPingService : BackgroundService
{
private readonly ILogger<TimedPingService> _logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
_logger.LogInformation("Pinging at: {time}", DateTime.UtcNow);
}
}
}
4. 优雅关闭 始终尊重取消令牌。不要无限期阻塞。
同时,记录关闭步骤:
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Stopping background worker");
await base.StopAsync(cancellationToken);
}
5. 后台处理的适用场景 ✅ 订单履行 ✅ 发送电子邮件/短信 ✅ 处理支付 ✅ 将文件上传到云存储 ✅ 清理旧数据 ✅ 重试队列
6. 技巧与最佳实践
后台处理不仅仅是一种性能优化技巧——它是构建健壮.NET应用程序的基本架构。
无论你是使用队列进行解耦、使用计时器进行计划工作,还是仅仅将繁重任务与用户请求分离,ASP.NET Core 都为你提供了正确的工具来实现。