You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core后台Worker类新容器范围下DatabaseContext已释放错误排查

.NET Core DI范围导致DatabaseContext已释放错误的分析与修复

问题根源

  1. 作用域提前释放:原代码在循环迭代内创建await using作用域,启动Fire&Forget任务后,循环迭代立即结束,作用域被自动Dispose,而后台任务仍在运行,导致其中注入的DatabaseContext实例已被释放,触发Cannot access a disposed context instance错误。
  2. Task.Factory.StartNew的不当使用:该方法不适合异步场景,且未绑定取消令牌,无法正确响应服务停止信号,同时未处理任务内部异常,存在未观察异常风险。
  3. 闭包捕获问题:直接在任务中引用循环变量uploadEntry,可能导致多个任务捕获到同一变量的最新值,引发逻辑错误。

修复方案

  1. 将作用域创建移至后台任务内部:确保作用域的生命周期覆盖整个任务执行过程,直到任务完成才释放DbContext。
  2. 使用Task.Run替代Task.Factory.StartNew:更适合异步任务调度,支持绑定取消令牌,简化异步操作处理。
  3. 异步方法必须await:如果ValidationGovernor.Complete是异步方法,必须使用await等待其执行完成,避免作用域提前释放。
  4. 捕获循环变量副本:避免闭包导致的变量引用问题。
  5. 处理任务内部异常:防止未观察到的异常导致服务崩溃。

修正后的代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 00:35:32