.NET Core - 具有新容器范围的后台工作线程类在 DI 注入的数据库上下文中出现“上下文处置”错误

问题描述 投票:0回答:1

我花了很多时间试图解决这个问题,但我认为我错过了一些东西。这是关于 .NET Core 6/8 和 ASP.Net 管道;故障出在 DI 的范围上,非常感谢任何帮助:

主要有 3 名玩家参与了这次(错误的)互动;在启动序列(Program.cs)中:

  • 使用“范围”生命周期注册的数据库上下文(请注意它正在使用实现工厂!)。
serviceCollection.AddScoped(sp => new DatabaseContext(configuration)); //configuration is a value type for r.t. params
  • 有一堆为同一类型注册的服务实现(接口:IValidationStep)——也有“范围”,并且州长服务也有“范围” (这是为了为一系列验证器实现某种工作流程)
  serviceCollection.AddScoped<IValidationStep,ConcreteValidatorOne>();
  serviceCollection.AddScoped<IValidationStep,ConcreteValidatorTwo>();
  . . .
  serviceCollection.AddScoped<IValidationGovernor,ValidationGovernor>();
  • 最后,有一个工作服务(显然是一个“单例”)将等待内存中的 pub-sub 通道获取消息并将它们分派给州长:(pub-sub 通道也是一个单例服务。AddSingleton (),围绕 System.Threading.Channel 队列构建)。
 serviceCollection.AddHostedService<UploadStagingService>();

现在,就依赖关系而言:

  • 每个验证器都依赖于一个数据库上下文实例(以更新状态):构造函数 DI-ed
  • Governor 依赖于一个
    IEnumerable<IValidationStep>
    集合:构造函数 DI-ed
  • 最后,后台服务 *UploadStagingService *依赖于 ValidationGovernor 的实例;它将以“即发即忘”的方式调用(启动任务)。

我希望下面代码中的所有迭代都能顺利执行;但是,所有依赖项均已正确解析,但 DatabaseContext 除外,该数据库错误会出现“无法访问已处置的上下文实例”错误:

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;
        _fileProcessingQueue = boundedMessageChannel;
        _scopeFactory = scopeFactory;
    }


    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            await using var _asyncServiceScope = _scopeFactory.CreateAsyncScope();

       await foreach (var  uploadEntry in _fileProcessingQueue.ReadAllAsync().WithCancellation(stoppingToken))
            {
                var dbContext = _asyncServiceScope.ServiceProvider.GetRequiredService<DatabaseContext>();
                   var validators =  _asyncServiceScope.ServiceProvider.GetServices<IValidationStep>();
           var governor =  _asyncServiceScope.ServiceProvider.GetService<IValidationGovernor>();
                try
                {
                    _ = Task.Factory.StartNew(() => governor.Complete(uploadEntry));
                }
                catch (Exception ex) {
                   ...
                }
            }
        }
        catch (OperationCanceledException)     {       _logger.LogDebug("whatever");        }
        catch (Exception ex)                 {       _logger.LogError(ex, "...");         }
        finally 
        {
            _logger.LogCritical("Fatal Error! Closing the Upload-processing Channel. This service instance should be recycled !");
            _uploadProcessingQueue.TryCompleteWriter();
        }
    }
}
dependency-injection asp.net-core-webapi background-task di-containers
1个回答
0
投票

您遇到此问题是因为未正确使用示波器;它应该为每个工作单元创建和处置,而不是在

ExecuteAsync
方法的生命周期内创建和处置。

要解决此问题,您可以尝试修改代码,如下所示:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            await foreach (var uploadEntry in _uploadProcessingQueue.ReadAllAsync().WithCancellation(stoppingToken))
            {
                using var scope = _scopeFactory.CreateScope();
                var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
                var validators = scope.ServiceProvider.GetServices<IValidationStep>();
                var governor = scope.ServiceProvider.GetRequiredService<IValidationGovernor>();
                var task = Task.Run(() => governor.Complete(uploadEntry, dbContext, validators), stoppingToken);

                try
                {
                    await task;
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Error processing uploadEntry: {uploadEntry}", uploadEntry);
                }
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogDebug("Upload processing cancelled.");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in ExecuteAsync of UploadStagingService.");
        }
        finally
        {
            _logger.LogCritical("Upload-processing channel is being completed. This service instance should be recycled!");
            _uploadProcessingQueue.TryCompleteWriter();
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.