如何优化WCF中System.ServiceModel.ChannelFactory的CreateFactory性能?
嘿,我之前也碰到过WCF通道初始化慢到让人崩溃的情况,尤其是第一次创建的时候。结合你的问题和调用栈来看,核心问题确实是XmlSerializer在首次使用时的反射和动态代码生成开销——哪怕你用了sgen,可能也没让WCF正确用上预生成的序列化程序集。下面给你几个可行的解决方案,按优先级排序:
1. 确保sgen生成的序列化程序集被WCF正确识别
sgen生成的程序集命名格式是[你的服务程序集名称].XmlSerializers.dll,比如你的TestClient所在程序集是MyWcfClient.dll,那生成的应该是MyWcfClient.XmlSerializers.dll。如果WCF没自动加载它,你可以手动指定:
var client = new TestClient(); // 获取Endpoint上的XmlSerializer行为 var xmlBehavior = client.ChannelFactory.Endpoint.Behaviors.Find<XmlSerializerOperationBehavior>(); if (xmlBehavior != null) { // 手动指定预生成的序列化程序集 xmlBehavior.SerializerAssembly = Assembly.LoadFrom("MyWcfClient.XmlSerializers.dll"); // 或者直接引用程序集:xmlBehavior.SerializerAssembly = typeof(TestClient).Assembly; }
另外,生成序列化程序集时要确保覆盖所有需要序列化的Request/Response类型,sgen命令可以加/t:xmlserializer参数,并且针对你的服务契约程序集生成。
2. 替换XmlSerializer为DataContractSerializer(如果服务端兼容)
XmlSerializer的动态代码生成是性能瓶颈的主要原因,而DataContractSerializer(DCS)的初始化开销小很多,而且不需要生成临时程序集。如果服务端也支持DCS(比如服务端用的是DataContract而不是XmlSerializer的序列化方式),你可以修改服务契约的特性:
[ServiceContract] [DataContractFormat] // 替换原来的[XmlSerializerFormat] public interface ITestService { [OperationContract] Response Get(Request request); }
这样WCF会自动用DCS来序列化请求响应,彻底避开XmlSerializer的反射开销。
3. 预加载证书,优化证书查找性能
你当前用FindByIssuerName查找证书,这个方法在证书较多的商店里会很慢,而且每次创建客户端都要重新查找。建议提前缓存证书,改用更高效的FindByThumbprint:
// 全局缓存证书实例 private static readonly Lazy<X509Certificate2> _cachedCert = new Lazy<X509Certificate2>(() => { using var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadOnly); // 替换成你的证书指纹(注意去掉空格) var certs = store.Certificates.Find(X509FindType.FindByThumbprint, "ABC123DEF456...", false); return certs.Count > 0 ? certs[0] : throw new InvalidOperationException("证书未找到"); }); // 创建客户端时直接用缓存的证书 var client = new TestClient() { ClientCredentials = { UserName = { UserName = "testing", Password = "testing" }, UseIdentityConfiguration = true, ClientCertificate = { Certificate = _cachedCert.Value } } };
这样每次创建客户端时就不用再去遍历证书商店,能省不少时间。
4. 预初始化ChannelFactory的元数据
虽然你不能缓存ChannelFactory实例(因为每个实例对应不同凭据),但可以提前加载服务契约的元数据和绑定配置,避免在创建ChannelFactory时重复解析:
// 提前加载契约和绑定,全局缓存 private static readonly ContractDescription _testContract = ContractDescription.GetContract(typeof(ITestService)); private static readonly BasicHttpBinding _testBinding = new BasicHttpBinding(); // 替换成你的实际绑定类型 // 创建客户端时直接用预加载的元数据 var factory = new ChannelFactory<ITestService>(_testBinding, new EndpointAddress("你的服务地址")); factory.ClientCredentials.UserName.UserName = "testing"; factory.ClientCredentials.UserName.Password = "testing"; factory.ClientCredentials.ClientCertificate.Certificate = _cachedCert.Value; // 添加你的自定义行为 factory.Endpoint.EndpointBehaviors.Add(new CustomEndpointBehavior(new HeaderAdder())); var client = factory.CreateChannel(); client.Get(request);
这样ChannelFactory初始化时就不用再去解析配置文件或生成契约描述,能减少部分反射开销。
最后总结
优先尝试第1和第2种方案,这两个是解决XmlSerializer反射开销的核心。如果服务端不支持DCS,就确保sgen的程序集被正确加载。同时优化证书查找的性能,能进一步减少初始化时间。
内容的提问来源于stack exchange,提问作者Andreas Savva

