WCF 在调用期间等待 _TransparantProxyStub_CrossContext 函数时最大 CPU

WCF maxes CPU when waiting on _TransparantProxyStub_CrossContext function during call

在使用 WCF 调用 Cisco 的 AXL SOAP API 时,我的使用率越来越高 CPU。我首先使用从 wsdl 生成的 类 创建一个服务模型 clientbase。我正在使用 basichttpbinding 和 transfermode 作为缓冲。执行调用时,CPU 达到最大值,CPU 配置文件显示 96% 的 CPU 时间是在 _TransparentProxyStub_CrossContext@0 之后调用的 clr.dll base.Channel.getPhone(request); 等调用。更准确地说,该调用使进程 运行 所在的 CPU 核心最大化。

这是从 wsdl 生成的客户端创建的片段

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
public partial class AXLPortClient : System.ServiceModel.ClientBase<AxlNetClient.AXLPort>, AxlNetClient.AXLPort
{

    public AXLPortClient()
    {
    }

    public AXLPortClient(string endpointConfigurationName) : 
            base(endpointConfigurationName)
    {
    }

    ...

这是我创建客户端的方式:

public class AxlClientFactory : IAxlClientFactory
{
    private const string AxlEndpointUrlFormat = "https://{0}:8443/axl/";

    public AXLPortClient CreateClient(IUcClientSettings settings)
    {
        ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
        ServicePointManager.Expect100Continue = false;                      

        var basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
        basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

        basicHttpBinding.MaxReceivedMessageSize = 20000000;
        basicHttpBinding.MaxBufferSize = 20000000;
        basicHttpBinding.MaxBufferPoolSize = 20000000;

        basicHttpBinding.ReaderQuotas.MaxDepth = 32;
        basicHttpBinding.ReaderQuotas.MaxArrayLength = 20000000;
        basicHttpBinding.ReaderQuotas.MaxStringContentLength = 20000000;

        basicHttpBinding.TransferMode = TransferMode.Buffered;
        //basicHttpBinding.UseDefaultWebProxy = false;

        var axlEndpointUrl = string.Format(AxlEndpointUrlFormat, settings.Server);
        var endpointAddress = new EndpointAddress(axlEndpointUrl);
        var axlClient = new AXLPortClient(basicHttpBinding, endpointAddress);
        axlClient.ClientCredentials.UserName.UserName = settings.User;
        axlClient.ClientCredentials.UserName.Password = settings.Password;
        return axlClient;
    }
}

为 AXL API 生成的 wsdl 代码非常大。初始调用和后续调用都存在 CPU 问题,尽管后续调用速度更快。我还能做些什么来调试这个问题吗?有没有办法减少这种高 CPU 使用率?

更新

有关赏金的更多信息:

我已经像这样创建了 C# 类:

svcutil AXLAPI.wsdl AXLEnums.xsd AXLSoap.xsd /t:code /l:C# /o:Client.cs /n:*,AxlNetClient

您必须从呼叫管理器系统下载 Cisco AXL api 的 wsdl。我使用的是 API 的 10.5 版本。我相信主要的减速与 XML 处理有关。 api 的 WSDL 非常庞大,结果 类 产生了 538406 行代码!

更新 2

我已经打开所有级别的 WCF 跟踪。最大的时间差异是在 "A message was written" 和 "Sent a message over a channel" 之间的过程操作 activity 中,这两个操作之间几乎过了整整一分钟。其他活动(构建通道、打开客户端和关闭客户端)都执行得相对较快。

更新 3

我对生成的客户端进行了两项更改 类。首先,我从所有操作合同中删除了 ServiceKnownTypeAttribute。其次,我从一些可序列化 类 中删除了 XmlIncludeAtribute。这两项更改将生成的客户端的文件大小减少了 50% 以上,并且对测试时间的影响很小(在 70 秒的测试结果上减少了大约 10 秒)。

我还注意到我有大约 900 个针对单个服务接口和端点的操作合同。这是由于 AXL API 的 wsdl 将所有操作分组在一个命名空间下。我正在考虑将其分解,但这意味着创建多个客户端库,每个客户端库将实现一个简化的接口并最终破坏实现此 wcf 库的所有内容。

更新 4

看来操作次数是中心问题。我能够通过动词(例如获取、添加等)将操作和接口定义分离到他们自己的客户端和接口中(使用 sublime text 和正则表达式的非常缓慢的过程,因为 resharper 和 codemaid 无法处理仍然 250K+ 的大文件线)。对定义了大约 150 个操作的 "Get" 客户端进行的测试导致 getPhone 的执行时间为 10 秒,而之前的结果为 60 秒。这仍然比它应该的要慢很多,因为在 fiddler 中简单地制作这个操作会导致 2 秒的执行。解决方案可能会通过尝试进一步分离操作来进一步减少操作数。但是,这增加了一个新问题,即破坏所有使用此库作为单个客户端的系统。

我终于解决了这个问题。根本原因似乎确实是操作次数。将生成的客户端从 900 多个操作拆分为每个操作 12 个(遵循 this question 的指导)后,我能够将生成请求所花费的处理器时间减少到几乎为零。

这是优化从 Cisco 的 AXL wsdl 生成的服务客户端的最终过程:

像这样使用 wsdl 生成客户端代码:

svcutil AXLAPI.wsdl AXLEnums.xsd AXLSoap.xsd /t:code /l:C# /o:Client.cs /n:*,AxlNetClient

处理生成的客户端文件以分解为子客户端:

我创建了 this script 来处理生成的代码。此脚本执行以下操作:

  1. 删除 ServiceKnownTypeFaultContractXmlInclude 属性。

这些对 xml 处理很有用,但据我了解,生成的 classes 似乎不正确。例如,服务已知类型对于所有操作都是相同的,即使许多已知类型对于每个操作都是唯一的。这将生成的文件的总大小从 500K+ 行减少到 250K+,客户端实例化时间的性能略有提高。

  1. 从实现接口的客户端库中分离出接口和方法的操作契约。

  2. 创建子客户端,每个子客户端有 12 个操作及其各自的实现。

这些子客户端包含三个主要部分。第一部分是原始 clientbase 客户端的一部分 class。我希望这个解决方案向后兼容,所以我在这里有一些方法可以引用子客户端,这样对旧 super-client 的调用仍然可以通过调用新的子客户端来工作。如果引用了任何已实现的操作,静态获取访问器将启动子客户端。还为调用关闭或中止时添加了事件,以便子客户端仍然可以 运行 这些操作。

subclient的第二部分和第三部分是实现12个操作的接口和subclientclass

然后我从原始生成的客户端中删除了接口和客户端方法。我替换了原始客户端的客户端构造函数,以简单地存储绑定和端点数据供子客户端在需要时使用。关闭和中止调用被重新创建为每个子客户端在实例化时都会订阅的事件调用程序。

最后,我已将身份验证移至类似于 described here 的自定义端点行为。使用 IClientMessageInspector 立即发送身份验证 header 可以节省对服务器的一次往返调用,WCF 喜欢在服务器进行身份验证之前首先发送匿名请求。这让我大约增加了 2 秒,具体取决于服务器。

总体而言,我的性能从 70 秒提高到 2.5 秒。