30

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

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

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

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

有任何想法吗?

4

6 回答 6

38

以下内容来自VMWare 论坛上的此线程:

嗨伙计,

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

问题

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

换句话说,以下代码行:

_service = new VimService();

执行可能需要大约 50 秒。

原因

显然,.NETXmlSerializer使用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 秒有问题,可以尝试在应用程序开始时对此类进行虚拟实例化,以改善登录时间的用户体验。

于 2009-06-08T16:48:37.450 回答
1

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

于 2008-10-05T15:03:16.180 回答
1

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

于 2008-11-18T04:51:52.027 回答
1

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

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

于 2008-12-17T22:07:12.487 回答
0

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

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

this.Proxy = GlobalProxySelection.GetEmptyWebProxy();
于 2012-12-07T01:13:50.747 回答
0

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

生成代理.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

生成代理.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 文件将在客户端项目的子文件夹引用中重新生成。首次执行脚本后,您应该添加对新 dll 的引用。

于 2016-09-08T21:08:37.450 回答