.NET5环境下Hangfire调用DI注入的WCF客户端代理出现故障态问题
问题根因
你注册WCF客户端的代码存在本质错误:预先创建了一个CustomerUpdateStarTransportPortTypesClient全局实例,再通过AddTransient返回该固定实例,即使标注了Transient生命周期,实际容器每次返回的都是同一个单例实例。WCF客户端代理不是线程安全的,多线程并发调用或者上一次调用出错进入故障状态后,后续所有调用都会直接报错,这就是你碰到故障状态报错的核心原因。
兼容DI的修复方案
1. 修正服务注册逻辑
要实现真正的Transient生命周期,每次获取客户端都新建实例,把实例创建逻辑放到注册委托内部:
public static IServiceCollection AddCustomerUpdateClient(this IServiceCollection services, IConfigurationRoot config) { ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; // 绑定配置是固定的,可以复用不需要每次创建 BasicHttpsBinding binding = new BasicHttpsBinding() { SendTimeout = TimeSpan.FromMinutes(1), OpenTimeout = TimeSpan.FromMinutes(1), CloseTimeout = TimeSpan.FromMinutes(1), ReceiveTimeout = TimeSpan.FromMinutes(10), Namespace = "http://www.starstandards.org/webservices/2005/10/transport", TextEncoding = System.Text.Encoding.UTF8, UseDefaultWebProxy = true, }; binding.MaxReceivedMessageSize = 50000000; binding.ReaderQuotas.MaxArrayLength = 50000000; binding.ReaderQuotas.MaxStringContentLength = 50000000; binding.ReaderQuotas.MaxNameTableCharCount = 50000000; var endpointConfig = config.GetSection(nameof(EndPointConfig)); EndpointAddress endpointAddress = new EndpointAddress($"{endpointConfig[nameof(EndPointConfig.CuUrl)]}"); // 把客户端创建放到委托内,每次请求都新建实例 services.AddTransient(x => new CustomerUpdateStarTransportPortTypesClient(binding, endpointAddress)); return services; }
2. 增加WCF客户端状态校验与异常处理
WCF客户端调用前需要判断状态,故障状态直接销毁重建,调用结束后正确释放资源,同时修正OperationContextScope的异步使用问题:
修改CustomerUpdateService代码如下:
public class CustomerUpdateService : StarService, ICustomerUpdateService { private readonly IServiceProvider _serviceProvider; // 改为注入IServiceProvider,每次需要客户端时主动获取新实例 public CustomerUpdateService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } [OperationContract] public async Task<AcknowledgeCustomerInformationPayload> ProcessCustomerUpdateAsync( ProcessCustomerInformationPayload customerPayload, string siteCode, ApplicationSetttings appSettings, string token, string customerName ) { var fordHeader = new FordDealerIdentity() { SiteCode = siteCode }; DataContractSerializer dcs = new DataContractSerializer(typeof(FordDealerIdentity)); // 每次调用从容器拿新的客户端实例 var starClient = _serviceProvider.GetRequiredService<CustomerUpdateStarTransportPortTypesClient>(); try { if (starClient.State == CommunicationState.Faulted) { starClient.Abort(); // 故障则重新获取实例 starClient = _serviceProvider.GetRequiredService<CustomerUpdateStarTransportPortTypesClient>(); } using (OperationContextScope scope = new OperationContextScope(starClient.InnerChannel)) { AddHeaders(appSettings, fordHeader, dcs, token); var message = new ProcessMessage() { payload = customerPayload }; // 把await放到using块内部,避免OperationContext异步丢失 var result = await starClient.ProcessMessageAsync(null, message); return result.ProcessMessageResponse.payload; } } catch (CommunicationException ex) { starClient.Abort(); throw; } finally { if (starClient.State == CommunicationState.Opened) { starClient.Close(); } else if (starClient.State == CommunicationState.Faulted) { starClient.Abort(); } } } }
3. 可选高性能复用方案
如果要避免每次调用都新建客户端的性能损耗,可以实现线程安全的WCF代理池服务,每次从池里取可用实例,故障实例直接销毁,使用完成后归还到池。该方案同样完全兼容DI规范,适合调用量较高的场景。
内容的提问来源于stack exchange,提问作者CT1971
相关产品推荐
相关产品推荐

