后台任务 Background Service
大约 8 分钟约 2305 字
后台任务 Background Service
简介
BackgroundService 是 ASP.NET Core 中执行后台长时间运行任务的基础类。它基于 IHostedService 接口,在应用启动时自动开始、停止时优雅关闭。适用于消息队列消费、定时任务、数据同步、缓存预热等后台场景。
特点
基本用法
最简单的后台服务
/// <summary>
/// 最简单的 BackgroundService
/// </summary>
public class SimpleBackgroundService : BackgroundService
{
private readonly ILogger<SimpleBackgroundService> _logger;
public SimpleBackgroundService(ILogger<SimpleBackgroundService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("后台服务启动");
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("后台任务执行中... {Time}", DateTime.Now);
await Task.Delay(5000, stoppingToken);
}
_logger.LogInformation("后台服务停止");
}
}
// 注册
// builder.Services.AddHostedService<SimpleBackgroundService>();IHostedService 接口
/// <summary>
/// IHostedService — 更底层的前后台任务接口
/// </summary>
public class StartupHostedService : IHostedService
{
private readonly ILogger _logger;
public StartupHostedService(ILogger<StartupHostedService> logger)
{
_logger = logger;
}
// 应用启动时执行
public async Task StartAsync(CancellationToken ct)
{
_logger.LogInformation("启动初始化任务");
await Task.CompletedTask;
}
// 应用停止时执行
public async Task StopAsync(CancellationToken ct)
{
_logger.LogInformation("停止清理任务");
await Task.CompletedTask;
}
}实际应用场景
1. 消息队列消费者
/// <summary>
/// RabbitMQ 消息消费者后台服务
/// </summary>
public class RabbitMqConsumerService : BackgroundService
{
private readonly IConnection _connection;
private readonly IModel _channel;
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<RabbitMqConsumerService> _logger;
public RabbitMqConsumerService(
IConnectionFactory connectionFactory,
IServiceScopeFactory scopeFactory,
ILogger<RabbitMqConsumerService> logger)
{
_connection = connectionFactory.CreateConnection();
_channel = _connection.CreateModel();
_scopeFactory = scopeFactory;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_channel.QueueDeclare("order_queue", durable: true, exclusive: false, autoDelete: false);
_channel.BasicQos(prefetchSize: 0, prefetchCount: 10, global: false);
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += async (model, ea) =>
{
try
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
_logger.LogInformation("收到消息:{Message}", message);
// 使用 Scope 获取 Scoped 服务
using var scope = _scopeFactory.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
await orderService.ProcessOrderAsync(message);
// 确认消息
_channel.BasicAck(ea.DeliveryTag, multiple: false);
}
catch (Exception ex)
{
_logger.LogError(ex, "消息处理失败");
_channel.BasicNack(ea.DeliveryTag, multiple: false, requeue: true);
}
};
_channel.BasicConsume("order_queue", autoAck: false, consumer);
// 等待取消信号
await Task.Delay(Timeout.Infinite, stoppingToken);
}
public override void Dispose()
{
_channel?.Close();
_connection?.Close();
base.Dispose();
}
}2. 定时数据同步
/// <summary>
/// 定时同步数据的后台服务
/// </summary>
public class DataSyncService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<DataSyncService> _logger;
private readonly TimeSpan _syncInterval = TimeSpan.FromMinutes(5);
public DataSyncService(IServiceScopeFactory scopeFactory, ILogger<DataSyncService> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("数据同步服务启动");
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = _scopeFactory.CreateScope();
var syncService = scope.ServiceProvider.GetRequiredService<ISyncService>();
var result = await syncService.SyncAllAsync(stoppingToken);
_logger.LogInformation("数据同步完成:{Success} 成功,{Failed} 失败",
result.SuccessCount, result.FailureCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "数据同步异常");
}
await Task.Delay(_syncInterval, stoppingToken);
}
}
}3. 缓存预热
/// <summary>
/// 应用启动时预加载缓存
/// </summary>
public class CacheWarmupService : IHostedService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger _logger;
public CacheWarmupService(IServiceScopeFactory scopeFactory, ILogger<CacheWarmupService> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
public async Task StartAsync(CancellationToken ct)
{
_logger.LogInformation("开始缓存预热...");
using var scope = _scopeFactory.CreateScope();
var cache = scope.ServiceProvider.GetRequiredService<IMemoryCache>();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 预加载配置数据
var configs = await dbContext.SystemConfigs.ToDictionaryAsync(c => c.Key, c => c.Value, ct);
cache.Set("SystemConfigs", configs);
// 预加载热门数据
var hotProducts = await dbContext.Products
.Where(p => p.IsHot)
.Take(100)
.ToListAsync(ct);
cache.Set("HotProducts", hotProducts);
_logger.LogInformation("缓存预热完成");
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}4. 过期数据清理
/// <summary>
/// 定时清理过期数据
/// </summary>
public class DataCleanupService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<DataCleanupService> _logger;
public DataCleanupService(IServiceScopeFactory scopeFactory, ILogger<DataCleanupService> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 每天凌晨 3 点执行
var now = DateTime.Now;
var nextRun = now.Date.AddDays(1).AddHours(3);
var delay = nextRun - now;
_logger.LogInformation("下次清理时间:{NextRun}", nextRun);
await Task.Delay(delay, stoppingToken);
try
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 清理30天前的日志
var cutoffDate = DateTime.Now.AddDays(-30);
var deletedLogs = await dbContext.OperationLogs
.Where(l => l.CreatedTime < cutoffDate)
.ExecuteDeleteAsync(stoppingToken);
// 清理过期 Token
var deletedTokens = await dbContext.RefreshTokens
.Where(t => t.ExpireTime < DateTime.Now)
.ExecuteDeleteAsync(stoppingToken);
_logger.LogInformation("清理完成:{Logs} 条日志,{Tokens} 个 Token", deletedLogs, deletedTokens);
}
catch (Exception ex)
{
_logger.LogError(ex, "数据清理失败");
}
}
}
}5. 健康上报
/// <summary>
/// 定期向注册中心上报健康状态
/// </summary>
public class HealthReportService : BackgroundService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _config;
private readonly ILogger<HealthReportService> _logger;
public HealthReportService(
IHttpClientFactory httpClientFactory,
IConfiguration config,
ILogger<HealthReportService> logger)
{
_httpClientFactory = httpClientFactory;
_config = config;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var consulUrl = _config["Consul:Address"];
var serviceName = _config["Consul:ServiceName"];
var serviceId = _config["Consul:ServiceId"];
while (!stoppingToken.IsCancellationRequested)
{
try
{
var client = _httpClientFactory.CreateClient();
var healthUrl = $"{consulUrl}/v1/agent/check/pass/service:{serviceId}";
await client.PutAsync(healthUrl, null, stoppingToken);
_logger.LogDebug("健康上报成功:{Service}", serviceName);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "健康上报失败");
}
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
}
}优雅关闭
配置关闭超时
/// <summary>
/// 后台服务优雅关闭配置
/// </summary>
// Program.cs
builder.Services.AddHostedService<GracefulShutdownService>();
builder.Services.Configure<HostOptions>(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds(30); // 关闭超时
});
// 带优雅关闭的后台服务
public class GracefulShutdownService : BackgroundService
{
private readonly ILogger _logger;
private int _runningTasks = 0;
public GracefulShutdownService(ILogger<GracefulShutdownService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("服务启动");
stoppingToken.Register(() =>
{
_logger.LogInformation("收到停止信号,等待任务完成...");
});
while (!stoppingToken.IsCancellationRequested)
{
Interlocked.Increment(ref _runningTasks);
try
{
await ProcessAsync(stoppingToken);
}
finally
{
Interlocked.Decrement(ref _runningTasks);
}
await Task.Delay(1000, stoppingToken);
}
_logger.LogInformation("所有任务已完成,服务停止");
}
private async Task ProcessAsync(CancellationToken ct)
{
// 模拟处理
await Task.Delay(500, ct);
}
}DI 作用域注意事项
/// <summary>
/// 后台服务中正确使用 DI 的方式
/// </summary>
public class ScopedServiceExample : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public ScopedServiceExample(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// 错误!BackgroundService 是 Singleton,不能直接注入 Scoped 服务
// private readonly IOrderService _orderService; // 不要这样做
// 正确做法:每次操作创建新的 Scope
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _scopeFactory.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<ScopedServiceExample>>();
await orderService.ProcessPendingOrdersAsync(stoppingToken);
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}IHostedService vs BackgroundService
| 特性 | IHostedService | BackgroundService |
|---|---|---|
| 实现 | Start/Stop 方法 | ExecuteAsync 循环 |
| 适用 | 启动初始化、一次性任务 | 长时间运行、循环任务 |
| 生命周期 | 随应用 | 随应用 |
| 复杂度 | 低 | 中 |
| 取消支持 | 手动传递 | 内置 stoppingToken |
优点
缺点
总结
BackgroundService 适合轻量级的后台任务:消息消费、定时同步、缓存预热、健康上报等。复杂调度场景(Cron 表达式、持久化、重试)使用 Hangfire 或 Quartz。核心原则:Singleton 中访问 Scoped 服务必须用 IServiceScopeFactory。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
- Kubernetes 主题必须同时看资源对象、调度行为、网络暴露和配置分发。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
- 上线前检查镜像、命名空间、探针、资源限制、Service/Ingress 和配置来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
- 只会 apply YAML,不理解对象之间的依赖关系。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
- 继续补齐调度、网络策略、存储、GitOps 和平台工程能力。
适用场景
- 当你准备把《后台任务 Background Service》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《后台任务 Background Service》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《后台任务 Background Service》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《后台任务 Background Service》最大的收益和代价分别是什么?
