【问题标题】:Slow SoapHttpClientProtocol constructor慢 SoapHttpClientProtocol 构造函数
【发布时间】:2010-09-15 09:14:52
【问题描述】:

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

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

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

我尝试了 sgen.exe、ngen 和 XGenPlus 的各种组合(这需要几个小时并生成 500MB 的附加代码),但无济于事。我考虑过实现一个 Windows 服务,它的 CrmService 实例很少,可以在需要时提供,但这似乎太过分了。

有什么想法吗?

【问题讨论】:

  • 重新标记。问题与他用来发现问题的特定于作者的 CRM 集成问题的细节关系不大,而与 xml 序列化的启动性能有关。
  • 同意,尽管极其庞大的 Web 服务 API 可能对 mscrm 来说有些独特,并且该标签可能会吸引使用该平台解决相同问题的人。
  • SalesForce API WSDL 我的同样问题是 41kloc 和攀登

标签: c# .net performance xml-serialization soaphttpclientprotocol


【解决方案1】:

以下内容摘自 VMWare 论坛上的 this 线程:

大家好,

我们发现 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 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. 运行您的应用程序并验证 VimService 对象实例化时间是否减少。

附加说明

sgen 工具有点像一个黑盒子,它的行为会根据您在 Machine.config 文件中的内容而有所不同。例如,默认情况下,它应该输出优化的非调试代码,但并非总是如此。要了解该工具,请在步骤 3 中使用 /k 标志,这将导致它保留所有临时生成的文件,包括它生成的源文件和命令行选项文件。

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

【讨论】:

  • 我在使用 3rd 方网络服务器时遇到了类似的问题,实例化生成的代理代码 wsdl.exe 需要 10-15 秒。上述步骤将其缩短到 2 秒。
  • 本周我遇到了完全相同的问题,最初实例化 Web 服务客户端代理需要一段时间。但是,这里有一个重大问题。当您注释掉 Web 服务使用的类型的 XmlIncludeAttribute 属性时,客户端将不再知道如何序列化/反序列化这些类型。因此,当您需要向客户端传递或从客户端检索自定义类型时,此选项将不起作用。 Microsoft Dynamics CRM 服务是一个完美的例子,它包含大量自定义类型。
  • 不具建设性但感激的评论:代理实例化时间从 400 万下降到 4 秒。那谢谢啦。很多。
  • 我使用 Visual Studio 进行正则表达式搜索和替换。这是我使用的模式。搜索:(?<attr>\[System.Xml.Serialization.[^\]]*\]) 替换:/*${attr}*/
【解决方案2】:

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

【讨论】:

  • 它没有帮助,构造函数仍然很慢。我可以确认“*.XmlSerializers.dll”已构建。 Debug 和 Release 模式都是一样的。是否必须引用 XmlSerializers.dll 程序集?
  • 奇数。它应该只是自动加载。 .XmlSerializers.dll 文件是否与可执行文件在同一目录中,如果是,您是在更改当前工作目录还是什么?
  • 更正,正如 Alex 所描述的,自动使用 sgen 始终将构造函数时间从大约 15 秒减少到大约 7 秒。它在实际编译步骤中增加了类似的延迟(这是有道理的),因此对于编辑-编译-运行来说,这并不是一个很大的好处。剩下的 7 秒是由于我假设的 JIT'ing。
【解决方案3】:

我相信这不是 SGEN 问题。我查看了构造函数代码,发现它做了很多反射(基于类上的 XmlIncludeAttribute)。它反映在所有这些上,并且可能需要很长时间。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      我发现将代理设置为 null/Empty 会阻止代理自动检测的发生 - 这在初始调用中最多需要 7 秒:

      this.Proxy = GlobalProxySelection.GetEmptyWebProxy();
      

      【讨论】:

        【解决方案6】:

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

        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 "(?<attr>\[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 文件将在客户端项目的子文件夹 references 中重新生成。 首次执行脚本后,应添加对新 dll 的引用。

        【讨论】:

          猜你喜欢
          • 2017-06-11
          • 2014-01-20
          • 2010-12-11
          • 2020-12-22
          • 1970-01-01
          • 2012-06-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多