依赖注入中多次创建令牌导致GitHub Runner并行测试超时
Dataverse ServiceClient并行测试挂起问题
依赖注入配置
通过tokenProviderFunction注入Microsoft.PowerPlatform.Dataverse.Client的DI配置代码:
sc.AddSingleton<IOrganizationServiceAsync2, ServiceClient>(provider => { var config = ...; var factory = ...; var client = new ServiceClient( new Uri(config....), factory.GetTokenAsync, true, provider.GetService<ILogger<ServiceClient>>()) { MaxRetryCount = 5 }; return client; });
令牌获取逻辑
令牌工厂基于多个ConfidentialClientApplications(每个用户对应一个)实现连接池,令牌获取代码如下:
return (await _generator .AcquireTokenForClient(new[] { $"{resource}/.default" }) .ExecuteAsync(cancellationToken)).AccessToken;
问题现象
- 本地虚拟机、本地测试及开发环境运行正常,但在GitHub Runner上执行xUnit集成测试时,测试并行运行且创建多个Fixture或测试类的场景下,会在
_generator.AcquireTokenForClient(...).ExecuteAsync步骤挂起 - GitHub Runner上顺序运行测试时无异常
- 使用类Fixture或在测试类构造函数中初始化DI服务时会触发问题
- 添加详细日志后,执行
generator相关操作后无后续日志输出 - 添加
.ConfigureAwait(false)后能输出下一行日志"done",但整体仍会挂起 - 新建测试项目可复现:单个测试类正常,多个则失败;但将令牌操作放在每个测试用例中时可正常运行
- 尝试
ubuntu-latest和windows-latest的GitHub Action,问题表现一致 - GitHub Action工作流已设置超时,问题仍存在
解决方案
1. 避免同步上下文执行异步操作
xUnit的测试类构造函数、Fixture初始化是同步执行上下文,若在此阶段触发异步令牌获取(比如DI初始化ServiceClient时自动触发),极易因线程上下文死锁导致挂起。建议:
- 用
Lazy<T>包装ServiceClient,延迟到测试用例内部再初始化 - 不在构造函数/Fixture中直接获取需要异步初始化的服务,改为在每个测试用例中显式获取并触发初始化
2. 隔离ConfidentialClientApplication实例
并行测试场景下,共享ConfidentialClientApplications可能引发资源竞争或线程池耗尽:
- 为每个测试用例创建独立的
ConfidentialClientApplication实例,避免跨测试共享状态 - 在xUnit配置文件中设置
maxParallelThreads,限制并行测试的线程数,适配GitHub Runner的资源限制
3. 确保CancellationToken正确传递
检查GetTokenAsync方法中是否正确传递测试框架提供的CancellationToken,避免因取消令牌未正确处理导致的无响应:
// 在测试用例中传递测试的CancellationToken var token = cancellationToken; var accessToken = await factory.GetTokenAsync(resource, token);
4. 调整ServiceClient初始化参数
初始化ServiceClient时,将自动刷新令牌的参数设为false(原代码第三个参数为true),减少后台异步操作带来的线程冲突:
var client = new ServiceClient( new Uri(config....), factory.GetTokenAsync, false, // 禁用自动令牌刷新 provider.GetService<ILogger<ServiceClient>>()) { MaxRetryCount = 5 };
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

