源文链接:https://mp.weixin.qq.com/s/FqoIHkeaCnepwWITQJ0RBQ?scene=1&click_id=5
并非应用程序中的所有工作都应在HTTP请求期间完成。
长时间运行的任务、计划作业、重试队列、事件处理和定期维护更适合在后台处理。
在.NET中,后台处理可以通过以下方式实现:
-
• IHostedService -
• BackgroundService -
• Channel 或 ConcurrentQueue -
• 基于计时器的调度 -
• 消息代理(RabbitMQ、Azure Service Bus等)
本指南将重点介绍如何使用.NET内置功能构建健壮的后台服务——无需外部框架。
1. IHostedService 和 BackgroundService
ASP.NET Core 为后台工作提供了一个清晰的抽象接口:IHostedService。更简单且首选的方式是继承 BackgroundService。
示例:基础后台工作者
public classFileCleanupService : BackgroundService
{
privatereadonly 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 interfaceIBackgroundTaskQueue
{
void Enqueue(Func<CancellationToken, Task> work);
Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken token);
}
publicclassBackgroundTaskQueue : IBackgroundTaskQueue
{
privatereadonly Channel<Func<CancellationToken, Task>> _queue =
Channel.CreateUnbounded<Func<CancellationToken, Task>>();
public void Enqueue(Func<CancellationToken, Task> work)
{
_queue.Writer.TryWrite(work);
}
publicasync Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken token)
{
returnawait _queue.Reader.ReadAsync(token);
}
}
步骤2:处理任务的后台服务
public classQueuedHostedService : BackgroundService
{
privatereadonly ILogger<QueuedHostedService> _logger;
privatereadonly 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. 技巧与最佳实践
-
• 使用 Channel 而不是 ConcurrentQueue 以实现异步友好的队列 -
• 将后台服务注册为 HostedService,而不是 Scoped -
• 记录启动和关闭步骤以实现可观测性 -
• 始终将后台作业包装在 try/catch 中 -
• 避免在 BackgroundService 中使用 Task.Run()
后台处理不仅仅是一种性能优化技巧——它是构建健壮.NET应用程序的基本架构。
无论你是使用队列进行解耦、使用计时器进行计划工作,还是仅仅将繁重任务与用户请求分离,ASP.NET Core 都为你提供了正确的工具来实现。