.NET后台处理完全指南:从IHostedService到队列架构的实战策略

作者:微信公众号:【架构师老卢】
9-23 14:4
165

并非应用程序中的所有工作都应在HTTP请求期间完成。

长时间运行的任务、计划作业、重试队列、事件处理和定期维护更适合在后台处理。

在.NET中,后台处理可以通过以下方式实现:

  • IHostedService
  • BackgroundService
  • Channel 或 ConcurrentQueue
  • 基于计时器的调度
  • 消息代理(RabbitMQ、Azure Service Bus等)

本指南将重点介绍如何使用.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. 技巧与最佳实践

  • 使用 Channel 而不是 ConcurrentQueue 以实现异步友好的队列
  • 将后台服务注册为 HostedService,而不是 Scoped
  • 记录启动和关闭步骤以实现可观测性
  • 始终将后台作业包装在 try/catch 中
  • 避免在 BackgroundService 中使用 Task.Run()

后台处理不仅仅是一种性能优化技巧——它是构建健壮.NET应用程序的基本架构。

无论你是使用队列进行解耦、使用计时器进行计划工作,还是仅仅将繁重任务与用户请求分离,ASP.NET Core 都为你提供了正确的工具来实现。

相关留言评论
昵称:
邮箱:
阅读排行