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

xunit.Microsoft.DependencyInjection单例实例异常及并行测试问题

单例模式单元测试问题排查与解决

代码示例

测试类代码

public class SingletonThreadSafeDependencyInjectionTests : TestBed<TestStartup>
...
[Fact]
public void MultiThreadedInstanceShouldReturnSameInstance()
{
    SingletonTestHelpers.RunMultiThreadTests(
        () => _fixture.GetService<SingletonThreadSafeDependencyInjection>(_testOutputHelper));
}

测试工具类代码

public class SingletonTestHelpers
...
//arrange
var integerCount = new int[3];
var instances = new List<SingletonBase>();
var options = new ParallelOptions() { MaxDegreeOfParallelism = 10 };

//act
Parallel.ForEach(integerCount, options, instance =>
{
    instances.Add(singletonBase.Invoke());
});

//assert
instances[0].Id.ShouldBe(instances[1].Id);
instances[2].Id.ShouldBe(instances[1].Id);
instances[0].Id.ShouldBe(instances[2].Id);

问题分析与解决

问题1:Parallel.ForEach执行次数不稳定,抛出数组索引越界异常

原因:List<T>并非线程安全集合,多线程环境下调用Add方法会引发竞争条件,导致元素未被正确添加到集合中,最终集合元素数量不足3个,触发索引越界。

解决方案:

  • 使用线程安全集合替代List<T>,比如ConcurrentBag<SingletonBase>:
    var instances = new ConcurrentBag<SingletonBase>();
    
    断言时转为数组再验证:
    var instanceArray = instances.ToArray();
    instanceArray[0].Id.ShouldBe(instanceArray[1].Id);
    instanceArray[2].Id.ShouldBe(instanceArray[1].Id);
    instanceArray[0].Id.ShouldBe(instanceArray[2].Id);
    
  • 或者在Add操作时加锁:
    object lockObj = new object();
    Parallel.ForEach(integerCount, options, instance =>
    {
        var singleton = singletonBase.Invoke();
        lock(lockObj)
        {
            instances.Add(singleton);
        }
    });
    

问题2:多线程下获取到不同的单例实例

原因:

  1. 单例类本身实现不具备线程安全性,比如未采用双重检查锁定、静态构造函数等线程安全的单例实现方式;
  2. 依赖注入容器中,该类生命周期未注册为Singleton,被错误设置为Scoped或Transient;
  3. 获取服务的逻辑存在线程安全漏洞,导致每次调用都创建新实例。

解决方案:

  1. 修复单例类实现,采用线程安全模式,比如双重检查锁定:
    public class SingletonThreadSafeDependencyInjection : SingletonBase
    {
        private static SingletonThreadSafeDependencyInjection _instance;
        private static readonly object _lockObj = new object();
    
        private SingletonThreadSafeDependencyInjection() { }
    
        public static SingletonThreadSafeDependencyInjection GetInstance()
        {
            if (_instance == null)
            {
                lock(_lockObj)
                {
                    if (_instance == null)
                    {
                        _instance = new SingletonThreadSafeDependencyInjection();
                    }
                }
            }
            return _instance;
        }
    }
    
  2. 确认DI容器注册逻辑,在TestStartup中把该类注册为单例:
    services.AddSingleton<SingletonThreadSafeDependencyInjection>();
    
  3. 验证_fixture.GetService的实现,确保每次调用都是从容器获取已存在的单例实例,而非新建实例。

内容的提问来源于stack exchange,提问作者Mehdi Zeynalov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:52:50