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

使用BuildServiceProvider引发单例多实例的替代实现方案咨询

问题描述

我尝试创建作为Background Service运行的依赖注入管道服务,希望能创建多个此类服务,通过传入简单的IOption配置设置每个步骤的可变值(按服务键加载),并将它们注册为键控服务避免相互覆盖。

同时我已将IQueueManager注册为单例,它可基于传入的IOptions配置获取生产者/消费者。

为每个管道版本传入不同服务键,核心简化代码如下:

public IServiceCollection AddPipelineService(this IServiceCollection services, 
IConfigurationManager configuration, string serviceKey)
{
 // 根据服务键获取步骤配置
 var stepOptions = Options.Create(
     configuration
     .GetSection($"{serviceKey}:{StepOptions.StepOptionsName}")
     .Get<MessageFactoryOptions>());

 // 构建步骤列表并注册服务 
 services.AddKeyedSingleton<IStep, Step1>(serviceKey, (p,o) => new Step1(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions));
 services.AddKeyedSingleton<IStep, Step2>(serviceKey, (p, o) => new Step2(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions));
 services.AddKeyedSingleton<IStep, Step3>(serviceKey, (p, o) => new Step3(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions));

 // 创建服务提供程序
 var provider = services.BuildServiceProvider(validateScopes: true);

 // 将步骤添加到集合中
 IEnumerable<IStep> steps = provider.GetKeyedServices<IMessageProcessItem>(serviceKey);
 services.AddKeyedSingleton<IStepCollection, StepCollection>(serviceKey, (p, o) => new StepCollection(steps));

 // 根据服务键创建队列配置
 var queueOptions = Options.Create(
     configuration
     .GetSection($"{serviceKey}:{QueueOptions.QueueOptionsName}")
     .Get<MessageFactoryOptions>());

 // 说明
 //  IQueueManager已提前注册为单例(我只想打开一个连接并复用它来创建消费者和生产者)
 //  PipelineBackgroundService是BackgroundService

 services.AddSingleton<IHostedService, PipelineBackgroundService>(
     p => new PipelineBackgroundService(
         p.GetRequiredService<ILogger<PipelineBackgroundService>>(),
         p.GetRequiredService<IQueueManager>(),
         p.GetRequiredKeyedService<IStepCollection>(serviceKey),
         queueOptions));

 return services;
}

目前代码可运行,但因调用BuildServiceProvider创建新容器,导致IQueueManager出现多个实例。已知应避免使用BuildServiceProvider,求无此副作用的替代实现方式?

解决方案

核心思路是延迟获取步骤集合,不在注册阶段构建服务提供者,而是让StepCollection在首次被解析时,从最终的根服务提供者中获取对应服务键的所有IStep实例。

1. 重构StepCollection的构造方式

修改StepCollection,让它依赖IServiceProvider和服务键,在内部延迟加载步骤:

public class StepCollection : IStepCollection
{
    private readonly IServiceProvider _serviceProvider;
    private readonly string _serviceKey;
    private IEnumerable<IStep>? _steps;

    public StepCollection(IServiceProvider serviceProvider, string serviceKey)
    {
        _serviceProvider = serviceProvider;
        _serviceKey = serviceKey;
    }

    public IEnumerable<IStep> GetSteps()
    {
        return _steps ??= _serviceProvider.GetKeyedServices<IStep>(_serviceKey).ToList();
    }
}

(如果原StepCollection是直接存储IEnumerable<IStep>,可以调整为通过方法获取,或者在构造时立即解析,但要确保是在最终服务提供者中完成解析)

2. 修改服务注册逻辑

移除BuildServiceProvider的调用,改为注册StepCollection时注入IServiceProvider和当前服务键:

public IServiceCollection AddPipelineService(this IServiceCollection services, 
IConfigurationManager configuration, string serviceKey)
{
 // 根据服务键获取步骤配置
 var stepOptions = Options.Create(
     configuration
     .GetSection($"{serviceKey}:{StepOptions.StepOptionsName}")
     .Get<MessageFactoryOptions>());

 // 构建步骤列表并注册服务 
 services.AddKeyedSingleton<IStep, Step1>(serviceKey, (p,o) => new Step1(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions));
 services.AddKeyedSingleton<IStep, Step2>(serviceKey, (p, o) => new Step2(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions));
 services.AddKeyedSingleton<IStep, Step3>(serviceKey, (p, o) => new Step3(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions));

 // 注册StepCollection,传入服务提供者和服务键,延迟加载步骤
 services.AddKeyedSingleton<IStepCollection, StepCollection>(serviceKey, (p, o) => 
     new StepCollection(p, serviceKey));

 // 根据服务键创建队列配置
 var queueOptions = Options.Create(
     configuration
     .GetSection($"{serviceKey}:{QueueOptions.QueueOptionsName}")
     .Get<MessageFactoryOptions>());

 // 注册BackgroundService
 services.AddSingleton<IHostedService, PipelineBackgroundService>(
     p => new PipelineBackgroundService(
         p.GetRequiredService<ILogger<PipelineBackgroundService>>(),
         p.GetRequiredService<IQueueManager>(),
         p.GetRequiredKeyedService<IStepCollection>(serviceKey),
         queueOptions));

 return services;
}

3. 可选:使用工厂注册替代直接实例化

如果不想让StepCollection依赖IServiceProvider,可以使用工厂委托,在注册时明确解析步骤:

services.AddKeyedSingleton<IStepCollection>(serviceKey, (sp, key) =>
{
    var steps = sp.GetKeyedServices<IStep>(serviceKey);
    return new StepCollection(steps);
});

这种方式同样不需要提前构建服务提供者,因为工厂委托会在最终服务提供者初始化时执行。

方案优势

  • 不再调用BuildServiceProvider,避免创建额外容器实例,确保IQueueManager始终为同一个单例。
  • 步骤集合的解析延迟到应用启动后、首次使用时,此时使用的是根服务提供者,所有单例服务都只有一个实例。

内容的提问来源于stack exchange,提问作者Nick Tucker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 13:55:53