CI环境dotnet并行测试加载卫星附属程序集偶发异常如何解决?
问题成因
- 核心根因是NET 5及更早版本中
Assembly.GetSatelliteAssembly方法本身不具备线程安全性:当多个线程并行调用该方法加载同一区域的附属程序集时,内部的程序集加载逻辑会触发竞态条件——第一个线程尚未完成附属程序集的加载/落盘流程,第二个线程就发起读取请求,就会抛出文件找不到的异常。 - 仅在CI环境触发的原因:本地开发、生产环境的并行度普遍较低,或者资源加载逻辑不会被高频并发触发;而CI环境执行xunit测试时默认开启多线程并行执行,大量用例同时调用资源加载逻辑就会复现偶发问题。
- 你当前使用的
mcr.microsoft.com/dotnet/sdk:5.0.401恰好属于存在该问题的版本,该线程安全缺陷在.NET 6及后续版本中已经被官方修复。
修复方案
方案1:加锁保护资源加载逻辑(改造成本最低)
给GetSatelliteAssembly的调用逻辑加全局锁,避免并发触发竞态问题,改造后代码示例:
// 定义静态锁对象 private static readonly object _satelliteAssemblyLock = new object(); // 改造原有加载逻辑 var currentAssembly = Assembly.GetExecutingAssembly(); var targetCulture = CultureInfo.CreateSpecificCulture(language); Assembly satelliteAssembly; lock (_satelliteAssemblyLock) { satelliteAssembly = currentAssembly.GetSatelliteAssembly(targetCulture); } using (Stream stream = satelliteAssembly.GetManifestResourceStream(resourceName)) { // 保持原有业务逻辑不变 }
方案2:关闭xunit测试并行执行(无需修改业务代码)
如果不想改动业务逻辑,可以在测试项目的xunit.runner.json配置文件中设置parallelizeTestCollections为false,或者执行测试命令时添加参数dotnet test --parallel none,缺点是测试整体执行速度会变慢。
方案3:升级.NET版本(永久修复)
把项目及CI环境的SDK版本升级到.NET 6或更高版本,官方已经修复了GetSatelliteAssembly的线程安全问题,无需修改业务代码即可解决问题。
内容的提问来源于stack exchange,提问作者Dejan
相关产品推荐
相关产品推荐

