ASP.NET Core 6 Web API中Autofac与BackgroundService协同使用问题排查
问题分析与解决方案
核心原因
控制器注入的repoService属于请求生命周期范围(LifetimeScope),当控制器返回200状态码后,该生命周期范围会被Autofac释放,关联的RepositoryManagerDbContext也随之销毁。后台任务执行时调用绑定了已释放范围的repoService,就会触发ObjectDisposedException。
修复方案
通过在后台任务执行时创建独立的生命周期范围,解析全新的服务实例,确保DbContext未被释放。
1. 修改BackgroundTaskService,注入Autofac根生命周期范围
using Autofac; using Microsoft.AspNetCore.SignalR; public class BackgroundTaskService : BackgroundService { private readonly Channel<BackgroundTask> _taskQueue; private readonly ILifetimeScope _rootLifetimeScope; // 注入Autofac根生命周期范围 public BackgroundTaskService(ILifetimeScope rootLifetimeScope) { _rootLifetimeScope = rootLifetimeScope; _taskQueue = Channel.CreateUnbounded<BackgroundTask>(); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await foreach (var backgroundTask in _taskQueue.Reader.ReadAllAsync(stoppingToken)) { Console.WriteLine($"Starting task {backgroundTask.Id}"); // 为每个任务创建独立的生命周期范围 using var taskScope = _rootLifetimeScope.BeginLifetimeScope(); // 根据任务类型执行对应逻辑 if (backgroundTask is CreateDeveloperEnvironmentTask devTask) { // 在新范围内解析服务 var repoService = taskScope.Resolve<IRepositoryService>(); var hubContext = taskScope.Resolve<IHubContext<SignalRHub>>(); await repoService.CreateDeveloperEnvironment(devTask.Request); await hubContext.Clients.All.SendAsync("ReceiveMessage", "CreateTaskRepo", new { Status = "Completed" }); } Console.WriteLine($"Completed task {backgroundTask.Id}"); } } // 提供特定任务的入队方法 public async Task EnqueueCreateDeveloperEnvironmentTask(SaveTaskDto request) { var task = new CreateDeveloperEnvironmentTask { Request = request }; Console.WriteLine($"Enqueueing task {task.Id}"); await _taskQueue.Writer.WriteAsync(task); } } // 抽象任务基类 public abstract class BackgroundTask { public Guid Id { get; } = Guid.NewGuid(); } // 具体任务类型 public class CreateDeveloperEnvironmentTask : BackgroundTask { public SaveTaskDto Request { get; set; } }
2. 修改控制器代码,使用新的任务入队方式
[HttpPost] public async Task<IActionResult> CreateTaskRepo(SaveTaskDto request) { await _backgroundTaskService.EnqueueCreateDeveloperEnvironmentTask(request); return Ok(); } // 移除原CreateDeveloperEnvironmentAndNotify方法,逻辑已移至BackgroundTaskService
3. 保持Autofac注册配置不变
Startup.cs的容器注册无需修改,BackgroundTaskService保持单例(HostedService要求),DbContext的InstancePerLifetimeScope配置保留:
public void ConfigureContainer(ContainerBuilder builder) { builder.RegisterType<BackgroundTaskService>() .AsSelf() .As<IHostedService>() .SingleInstance(); builder.RegisterModule<DatabaseModule>(); builder.RegisterModule<GitModule>(); builder.RegisterModule<ConfigurationModule>(); builder.RegisterModule<RedmineModule>(); builder.RegisterModule<ExternalPerspectivModule>(); builder.RegisterModule<RepositoryModule>(); builder.RegisterModule<DiskModule>(); builder.RegisterType<SignalRHub>(); }
DatabaseModule中的DbContext注册:
builder.RegisterType<RepositoryManagerDbContext>().AsSelf().InstancePerLifetimeScope();
修复原理
- 每个后台任务执行时,从Autofac根范围创建独立的子生命周期范围,在该范围内解析的所有服务(包括DbContext)都属于这个新范围。
- 任务完成后,子范围被自动释放,DbContext也会被正确回收,既避免了上下文已释放的问题,也符合EF Core的生命周期管理规范。
内容的提问来源于stack exchange,提问作者Kobi
相关产品推荐
相关产品推荐

