Azure Function中Dataverse ServiceClient多请求异常问题求助
.NET 8独立工作者模型Azure Function集成Dataverse多请求异常解决
问题背景
基于.NET 8独立工作者模型开发的Azure Function应用,包含多个HTTP触发函数,通过Dataverse ServiceClient(v1.1.17)对接Dynamics 365。并发请求时底层HttpClient会被意外释放,频繁抛出TaskCanceledException。
试过这些方法都无效:
- 将ServiceClient注册为Scoped或Singleton,结果无差异
- 每次请求新建ServiceClient实例并手动释放
- 参考CdsWeb仓库的封装方式,创建可复用实例副本
测试对比
- 相同代码在Minimal API和控制台应用循环运行完全正常
- k6负载测试(100虚拟用户运行20秒):
- Azure Function场景仅1次迭代成功,99次中断
- Minimal API场景100次迭代全部完成
- 部署到Azure消费计划后问题依然存在
核心代码
测试代码
var query = new QueryExpression(entityName: "contact") { TopCount = 1, ColumnSet = new ColumnSet("contactid", "firstname") }; // 注入的单例ServiceClient,AccessToken已在内存缓存复用 var results = await serviceClient.RetrieveMultipleAsync(query); var contactEntity = results.Entities.SingleOrDefault();
ServiceClient注册配置
// 注册到服务集合 services.AddSingleton<IOrganizationServiceAsync, ServiceClient>(provider => { var appSettings = provider.GetRequiredService<IOptions<AppSettings>>(); var cache = provider.GetRequiredService<IMemoryCache>(); var managedIdentity = new DefaultAzureCredential(new DefaultAzureCredentialOptions { // Azure环境使用托管标识 ManagedIdentityClientId = appSettings.Value.ManagedIdentityClientId, ExcludeManagedIdentityCredential = false, // 本地开发使用VS/VS Code凭据 ExcludeVisualStudioCredential = false, ExcludeVisualStudioCodeCredential = false }); var environment = appSettings.Value.DataverseInstanceUrl; return new ServiceClient( tokenProviderFunction: f => GetToken(environment, managedIdentity, cache), instanceUrl: new Uri(environment), useUniqueInstance: true); }); // 获取AccessToken并缓存50分钟 async static Task<string> GetToken(string environment, DefaultAzureCredential credential, IMemoryCache cache) { var accessToken = await cache.GetOrCreateAsync(environment, async (cacheEntry) => { cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(50); var token = await credential.GetTokenAsync(new TokenRequestContext([$"{environment}/.default"])); return token; }); return accessToken.Token; }
解决办法
1. 关闭useUniqueInstance参数
将ServiceClient构造中的useUniqueInstance改为false,该参数强制使用唯一HttpClient实例,在Azure Function动态资源环境下易被回收或出现线程冲突:
return new ServiceClient( tokenProviderFunction: f => GetToken(environment, managedIdentity, cache), instanceUrl: new Uri(environment), useUniqueInstance: false); // 修改此处
2. 改用Scoped生命周期
放弃单例注册,将ServiceClient注册为Scoped,让每个请求拥有独立实例,避免多线程共享冲突:
services.AddScoped<IOrganizationServiceAsync, ServiceClient>(provider => { // 保留原初始化逻辑,useUniqueInstance设为false var appSettings = provider.GetRequiredService<IOptions<AppSettings>>(); var cache = provider.GetRequiredService<IMemoryCache>(); var managedIdentity = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = appSettings.Value.ManagedIdentityClientId, ExcludeManagedIdentityCredential = false, ExcludeVisualStudioCredential = false, ExcludeVisualStudioCodeCredential = false }); var environment = appSettings.Value.DataverseInstanceUrl; return new ServiceClient( tokenProviderFunction: f => GetToken(environment, managedIdentity, cache), instanceUrl: new Uri(environment), useUniqueInstance: false); });
3. 手动控制HttpClient生命周期
自行注册HttpClient单例,替换ServiceClient内部的HttpClient,避免依赖其内部管理逻辑:
services.AddSingleton<HttpClient>(); services.AddSingleton<IOrganizationServiceAsync, ServiceClient>(provider => { var appSettings = provider.GetRequiredService<IOptions<AppSettings>>(); var cache = provider.GetRequiredService<IMemoryCache>(); var httpClient = provider.GetRequiredService<HttpClient>(); var managedIdentity = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = appSettings.Value.ManagedIdentityClientId, ExcludeManagedIdentityCredential = false, ExcludeVisualStudioCredential = false, ExcludeVisualStudioCodeCredential = false }); var environment = appSettings.Value.DataverseInstanceUrl; var serviceClient = new ServiceClient( tokenProviderFunction: f => GetToken(environment, managedIdentity, cache), instanceUrl: new Uri(environment), useUniqueInstance: false); // 替换内部HttpClient serviceClient.HttpClient = httpClient; return serviceClient; });
4. 添加重试策略
使用Polly添加重试逻辑,处理TaskCanceledException这类临时异常:
// 注册Polly重试策略 services.AddPolicyRegistry() .Add("DataverseRetry", Policy.Handle<TaskCanceledException>() .Or<HttpRequestException>() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))); // 在函数中使用策略 var retryPolicy = _policyRegistry.Get<IAsyncPolicy>("DataverseRetry"); await retryPolicy.ExecuteAsync(async () => { var results = await serviceClient.RetrieveMultipleAsync(query); contactEntity = results.Entities.SingleOrDefault(); });
原因总结
Azure Function的消费计划(包括本地宿主)会动态调整资源,单例ServiceClient内部的HttpClient易被宿主资源回收机制误释放;加上useUniqueInstance: true强制使用单一实例,高并发下会出现连接池耗尽或线程安全问题,最终导致HttpClient被异常释放。
内容的提问来源于stack exchange,提问作者camlCase
相关产品推荐
相关产品推荐

