慢SoapHttpClientProtocol构造函数

我正在使用Microsoft Dynamics CRM进行一些实验。 您通过Web服务与它进行交互,并且我已将Web引用添加到我的项目中。 Web服务接口非常丰富,生成的“Reference.cs”大约是90k loc。

我在控制台应用程序中使用Web引用。 我经常改变一些东西,重新编译并运行。 编译很快,但是CrmService service = new CrmService(); Web服务引用非常慢,花费大约15-20秒: CrmService service = new CrmService(); 分析显示所有时间都花在SoapHttpClientProtocol构造函数中。

罪魁祸首显然是XML序列化代码(不包括在上面提到的90k loc中)是在运行时生成的,然后才进行JIT。 这在构造函数调用期间发生。 在玩耍和尝试时,等待是相当令人沮丧的。

我尝试了sgen.exe,ngen和XGenPlus的各种组合(需要几个小时并生成500MB的额外代码),但无济于事。 我已经考虑过实现一个几乎没有CrmService实例的Windows服务,可以在需要的时候发布,但这似乎太过分了。

有任何想法吗?

以下内容是从VMWare论坛上的这个post中删除的:

嗨伙计,

我们发现sgen.exe确实有效。 除了预先生成我们在此线程中遗漏的序列化器dll之外,还有一些额外的步骤。 这是详细的说明

问题

使用.NET中的VIM 2.0 SDK需要很长时间来实例化VimService类。 (VimService类是通过运行’wsdl.exe vim.wsdl vimService.wsdl’生成的代理类)

换句话说,以下代码行:

 _service = new VimService(); 

可能需要大约50秒才能执行。

原因

显然,.NET XmlSerializer使用System.Xml.Serialization.*属性来注释代理类,以便在运行时生成序列化代码。 当代理类很多很大时,就像VimService.cs中的代码一样,序列化代码的生成可能需要很长时间。

这是Microsoft .NET序列化程序如何工作的已知问题。

以下是MSDN提供的有关解决此问题的一些参考:

http://msdn2.microsoft.com/en-us/library/bk3w6240.aspx http://msdn2.microsoft.com/en-us/library/system.xml.serialization.xmlserializerassemblyattribute.aspx

不幸的是,上述参考文献均未描述该问题的完整解决方案。 相反,他们专注于如何预生成XML序列化代码。

完整的修复包括以下步骤:

  1. 使用预生成的XML序列化程序代码创建程序集(DLL)

  2. 从代理代码中删除对System.Xml.Serialization。*属性的所有引用(即从VimService.cs文件中删除)

  3. 使用XmlSerializerAssemblyAttribute注释主代理类,以将其指向XML序列化程序集的位置。

跳过步骤2导致VimService类的实例化时间仅提高20%。 跳过步骤1或3会导致代码错误。 通过所有三个步骤,实现了98%的改进。

以下是分步说明:

在开始之前,请确保使用的是.NET verison 2.0工具。 此解决方案不适用于.NET 1.1版,因为sgen工具和XmlSerializationAssemblyAttribute仅在.NET 2.0版中可用

  1. 使用wsdl.exe从WSDL生成VimService.cs文件:

    wsdl.exe vim.wsdl vimService.wsdl

    这将在当前目录中输出VimService.cs文件

  2. 将VimService.cs编译到库中

    csc /t:library /out:VimService.dll VimService.cs

  3. 使用sgen工具预生成和编译XML序列化程序:

    sgen /p VimService.dll

    这将在当前目录中输出VimService.XmlSerializers.dll

  4. 返回VimService.cs文件并删除所有System.Xml.Serialization.*属性。 因为代码很大,实现它的最好方法是使用一些正则表达式替换工具。 这样做时要小心,因为并非所有属性都出现在一行上。 有些内容是方法声明的一部分。

    如果您觉得这一步很难,这里有一个简化的方法:

    假设您正在编写C#,请对以下字符串执行全局替换:

    [System.Xml.Serialization.XmlIncludeAttribute

    并替换为:

    // [System.Xml.Serialization.XmlIncludeAttribute

    这将通过评论它们来消除Xml.Serialization属性,这些属性是减速的最大罪魁祸首。 如果您正在使用其他一些.NET语言,只需根据该语言的语法修改替换后的字符串以进行前缀注释。 这种简化的方法将为您提供最大的加速。 删除其余的Xml.Serialization属性只能实现0.2秒的额外加速。

  5. 将以下属性添加到VimService.cs中的VimService类:

    [System.Xml.Serialization.XmlSerializerAssemblyAttribute(AssemblyName = "VimService.XmlSerializers")]

    你应该得到这样的东西:

    // ... Some code here ... [System.Xml.Serialization.XmlSerializerAssemblyAttribute(AssemblyName = "VimService.XmlSerializers")] public partial class VimService : System.Web.Services.Protocols.SoapHttpClientProtocol { // ... More code here

  6. 通过重新生成VimSerice.dll库

    csc /t:library /out:VimService.dll VimService.cs

  7. 现在,从您的应用程序中,您可以添加对VimSerice.dll库的引用。

  8. 运行您的应用程序并validationVimService对象的实例化时间是否减少。

补充说明

sgen工具有点像黑盒子,它的行为会因Machine.config文件中的内容而异。 例如,默认情况下,它应该是优化的非调试代码,但情况并非总是如此。 要获得对该工具的一些可见性,请在步骤3中使用/ k标志,这将使其保留所有临时生成的文件,包括它生成的源文件和命令行选项文件。

即使在上述修复之后,第一次实例化VimService类所花费的时间也不是瞬时的(1.5秒)。 根据经验观察,似乎剩余时间的大部分是由于处理SoapDocumentMethodAttribute属性。 目前尚不清楚如何减少这个时间。 预生成的XmlSerializer程序集不考虑与SOAP相关的属性,因此这些属性需要保留在代码中。 好消息是,只有该应用程序的VimService类的第一次实例化需要很长时间。 因此,如果额外的1.5秒是一个问题,可以尝试在应用程序开始时对此类进行虚拟实例化,以此作为改善用户登录时间体验的手段。

您可能希望查看.NET附带的Sgen.exe工具。 在Visual Studio的C#项目属性“Build”页面中,还有一个方便的小东西,在最底层,称为“构建序列化程序集”,它会自动为您运行Sgen

我认为这不是一个SGEN问题。 我查看了构造函数代码,我发现它正在进行大量的reflection(基于类上的XmlIncludeAttribute)。 它反映了所有这些,可能需要很长时间。

CRM附带了一个预生成的XmlSerializer程序集。 检查GAC中是否有SdkTypeProxy.XmlSerializers.dll和SdkProxy.XmlSerializers.dll。

如果不这样,则意味着当您创建CrmService时,.net将生成XmlSerializer程序集,这可能需要一些时间。 希望这可以帮助

我试图找出为什么我的初始SoapHttpClientProtocol调用花了这么长时间,我遇到了这个线程。

我发现将Proxy设置为null / Empty会停止Proxy AutoDetect的发生 – 这在初始调用时最多需要7秒:

 this.Proxy = GlobalProxySelection.GetEmptyWebProxy(); 

我已经使用了上面详细的答案作为指导,并向前迈了几步,制作了一个自动化流程的脚本。 脚本由两个文件组成:

generateproxy.bat:

 REM if your path for wsdl, csc or sgen is missing, please add it here (it varies from machine to machine) set PATH=%PATH%;C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools;C:\Program Files (x86)\MSBuild\14.0\Bin wsdl http://localhost:57237/VIM_WS.asmx?wsdl REM create source code out of WSDL PowerShell.exe -ExecutionPolicy Bypass -Command "& '%~dpn0.ps1'" REM proces source code (remove annotations, add other annotation, put class into namespace) csc /t:library /out:references\VIM_Service.dll VIM_WS.cs REM compile source into dll sgen /p references\VIM_Service.dll /force REM generate serializtion dll 

generateproxy.ps1

 (Get-Content VIM.cs) | ForEach-Object { $_ -replace "(?\[global::System.Xml.Serialization.[^\]]*\])", "/*${attr}*/" ` -replace "public partial class VIM", "[System.Xml.Serialization.XmlSerializerAssemblyAttribute(AssemblyName = ""VIM_Service.XmlSerializers"")] `npublic partial class VIM" ` -replace "using System;", "namespace Classes.WS_VIM { `n`nusing System;" } | Set-Content VIM.cs Add-Content VIM.cs "`n}" 

我已将这两个文件添加到客户端项目中,并且在预构建事件中我添加了行

 cd..\.. generateproxy 

因此,在每次构建之前,代理类都会重新生成,而开发人员(几乎)不需要考虑它。 构建时,WS必须启动并运行,其URL必须在bat文件中。 作为预构建的结果,两个dll文件将在客户端项目的子文件夹引用中重新生成。 首次执行脚本后,您应该添加对新dll的引用。