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:多线程下获取到不同的单例实例
原因:
- 单例类本身实现不具备线程安全性,比如未采用双重检查锁定、静态构造函数等线程安全的单例实现方式;
- 依赖注入容器中,该类生命周期未注册为
Singleton,被错误设置为Scoped或Transient; - 获取服务的逻辑存在线程安全漏洞,导致每次调用都创建新实例。
解决方案:
- 修复单例类实现,采用线程安全模式,比如双重检查锁定:
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; } } - 确认DI容器注册逻辑,在
TestStartup中把该类注册为单例:services.AddSingleton<SingletonThreadSafeDependencyInjection>(); - 验证
_fixture.GetService的实现,确保每次调用都是从容器获取已存在的单例实例,而非新建实例。
内容的提问来源于stack exchange,提问作者Mehdi Zeynalov
相关产品推荐
相关产品推荐

