.NET Core后台Worker类新容器范围下DatabaseContext已释放错误排查
.NET Core DI范围导致DatabaseContext已释放错误的分析与修复
问题根源
- 作用域提前释放:原代码在循环迭代内创建
await using作用域,启动Fire&Forget任务后,循环迭代立即结束,作用域被自动Dispose,而后台任务仍在运行,导致其中注入的DatabaseContext实例已被释放,触发Cannot access a disposed context instance错误。 - Task.Factory.StartNew的不当使用:该方法不适合异步场景,且未绑定取消令牌,无法正确响应服务停止信号,同时未处理任务内部异常,存在未观察异常风险。
- 闭包捕获问题:直接在任务中引用循环变量
uploadEntry,可能导致多个任务捕获到同一变量的最新值,引发逻辑错误。
修复方案
- 将作用域创建移至后台任务内部:确保作用域的生命周期覆盖整个任务执行过程,直到任务完成才释放DbContext。
- 使用Task.Run替代Task.Factory.StartNew:更适合异步任务调度,支持绑定取消令牌,简化异步操作处理。
- 异步方法必须await:如果
ValidationGovernor.Complete是异步方法,必须使用await等待其执行完成,避免作用域提前释放。 - 捕获循环变量副本:避免闭包导致的变量引用问题。
- 处理任务内部异常:防止未观察到的异常导致服务崩溃。
修正后的代码
public class UploadStagingService : BackgroundService { private readonly ILogger<UploadStagingService> _logger; private readonly UploadProcessingQueue _uploadProcessingQueue; private readonly IServiceScopeFactory _scopeFactory; public UploadStagingService(ILogger<UploadStagingService> logger, UploadProcessingQueue boundedMessageChannel, IServiceScopeFactory scopeFactory) { _logger = logger; _uploadProcessingQueue = boundedMessageChannel; // 修正原笔误 _scopeFactory = scopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { await foreach (var uploadEntry in _uploadProcessingQueue.ReadAllAsync().WithCancellation(stoppingToken)) { // 捕获循环变量副本,避免闭包问题 var currentEntry = uploadEntry; // 启动后台任务,将作用域创建移至任务内部 _ = Task.Run(async () => { await using var scope = _scopeFactory.CreateAsyncScope(); var governor = scope.ServiceProvider.GetRequiredService<IValidationGovernor>(); try { // 如果Complete是异步方法,必须await await governor.Complete(currentEntry); } catch (Exception ex) { _logger.LogError(ex, "处理上传条目失败,条目ID: {EntryId}", currentEntry.Id); } }, stoppingToken); } } catch (OperationCanceledException) { _logger.LogDebug("上传处理服务已取消"); } catch (Exception ex) { _logger.LogError(ex, "上传处理服务发生致命错误"); } finally { _logger.LogCritical("致命错误!关闭上传处理通道,服务实例需要重启"); _uploadProcessingQueue.TryCompleteWriter(); } } }
额外注意事项
- 确保
ValidationGovernor.Complete方法如果涉及异步操作(如DbContext的SaveChangesAsync),必须定义为async Task或async ValueTask,并在调用时使用await。 - 若需要严格的任务跟踪(如确保任务完成后再关闭服务),可以维护一个任务列表,在服务停止时等待所有任务完成:
private readonly List<Task> _runningTasks = new(); // ... var task = Task.Run(...); lock (_runningTasks) _runningTasks.Add(task); _ = task.ContinueWith(t => { lock (_runningTasks) _runningTasks.Remove(t); }, stoppingToken); // 在停止时 await Task.WhenAll(_runningTasks);
内容的提问来源于stack exchange,提问作者Zee Grinch
相关产品推荐
相关产品推荐

