必须用 IServiceScope 显式创建新作用域来使用 DbContext,因后台服务长生命周期且 DbContext 非线程安全;需在 ExecuteAsync 中每次循环创建作用域、获取上下文、执行操作、及时释放,并响应取消令牌。

在后台任务中使用 EF Core,核心是解决生命周期管理和数据库上下文线程安全性问题。直接在 IHostedService(尤其是 BackgroundService)里复用 Web 请求作用域的 DbContext 会出错——因为后台服务是长生命周期,而默认注册的 DbContext 是 Scoped(每个请求一个实例),不能跨线程/跨作用域共享。
必须用 IServiceScope 显式创建新作用域
后台服务运行在独立线程,不自动拥有作用域。你需要从 IServiceProvider 创建一个新作用域,在其内获取 DbContext,用完立即释放。
- 不要把
DbContext声明为类字段(避免跨周期复用) - 每次执行任务逻辑前,用
serviceProvider.CreateScope()开启新作用域 - 从该作用域中解析
DbContext,执行查询或保存 - 作用域对象(
IServiceScope)用using确保及时释放,触发DbContext释放和连接归还
推荐继承 BackgroundService 而非直接实现 IHostedService
BackgroundService 封装了启动/停止逻辑和取消令牌传递,更安全易用。你的任务逻辑写在 ExecuteAsync(CancellationToken) 中。
- 构造函数注入
IServiceProvider(不是DbContext) - 在
ExecuteAsync内部按需创建作用域和上下文 - 务必监听传入的
CancellationToken,在取消时及时退出循环并释放资源 - 加
await Task.Delay(..., cancellationToken)防止忙等
注意 DbContext 不是线程安全的
即使你在每次循环中都新建作用域和 DbContext,也不能在同一个 DbContext 实例上并发调用 SaveChangesAsync 或多个异步查询。
- 一个
DbContext实例只用于一次“单元工作”(比如查+改+保存) - 若需并行操作(如同时拉取多个 API 并分别存库),应为每个操作单独创建作用域和上下文
- 避免在
async/await链中跨await继续使用同一上下文(EF Core 6+ 对部分场景放宽,但不建议依赖)
示例:每 5 秒检查待处理订单
代码结构示意(省略异常处理和日志):
public class OrderProcessingService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public OrderProcessingService(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService();
var pendingOrders = await context.Orders
.Where(o => o.Status == OrderStatus.Pending)
.ToListAsync(stoppingToken);
foreach (var order in pendingOrders)
{
order.Status = OrderStatus.Processing;
// ... 其他业务逻辑
}
await context.SaveChangesAsync(stoppingToken);
}
catch (OperationCanceledException)
{
break; // 正常退出
}
catch (Exception ex)
{
// 记录日志,但不要 throw,否则 BackgroundService 会终止
}
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
注册方式(Program.cs):
services.AddHostedService();
基本上就这些。关键就是:不共享上下文、每次用都新开作用域、及时释放、响应取消信号。










