C# 慢 SoapHttpClientProtocol 构造函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/172095/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Slow SoapHttpClientProtocol constructor
提问by friism
I'm doing some experiments with Microsoft Dynamics CRM. You interact with it through web services and I have added a Web Reference to my project. The web service interface is very rich, and the generated "Reference.cs" is some 90k loc.
我正在用 Microsoft Dynamics CRM 做一些实验。您通过 Web 服务与它交互,并且我已向我的项目添加了一个 Web 引用。web服务接口非常丰富,生成的“Reference.cs”有90k左右。
I'm using the web reference in a console application. I often change something, recompile and run. Compilation is fast, but newing up the web service reference is very slow, taking some 15-20 seconds:
CrmService service = new CrmService();
Profiling reveals that all time is spent in the SoapHttpClientProtocol constructor.
我在控制台应用程序中使用 Web 参考。我经常改变一些东西,重新编译并运行。编译速度很快,但更新 Web 服务引用非常慢,大约需要 15-20 秒:
CrmService service = new CrmService();
分析显示所有时间都花在 SoapHttpClientProtocol 构造函数中。
The culprit is apparently the fact that the XML serialization code (not included in the 90k loc mentioned above) is generated at run time, before being JIT'ed. This happens during the constructor call. The wait is rather frustrating when playing around and trying things out.
罪魁祸首显然是 XML 序列化代码(不包括在上面提到的 90k loc 中)是在运行时生成的,然后是 JIT。这发生在构造函数调用期间。在玩耍和尝试时,等待是相当令人沮丧的。
I've tried various combinations of sgen.exe, ngen and XGenPlus (which takes several hours and generates 500MB of additional code) but to no avail. I've considered implementing a Windows service that have few CrmService instances ready to dish out when needed but that seems excessive.
我尝试了 sgen.exe、ngen 和 XGenPlus 的各种组合(这需要几个小时并生成 500MB 的附加代码),但无济于事。我已经考虑过实现一个 Windows 服务,该服务几乎没有准备好在需要时提供的 CrmService 实例,但这似乎过度了。
Any ideas?
有任何想法吗?
采纳答案by Chris Marasti-Georg
The following is ripped from thisthread on the VMWare forums:
以下内容摘自VMWare 论坛上的此线程:
Hi folks,
嗨伙计,
We've found that sgen.exe does work. It'just that there is a couple of additional steps beyond pre-generating the serializer dll's that we missed in this thread. Here is the detailed instruction
我们发现 sgen.exe 确实有效。只是除了预先生成我们在此线程中遗漏的序列化程序 dll 之外,还有几个额外的步骤。这是详细的说明
PROBLEM
问题
When using the VIM 2.0 SDK from .NET requires long time to instantiate the VimService class. (The VimService class is the proxy class generated by running 'wsdl.exe vim.wsdl vimService.wsdl')
从 .NET 使用 VIM 2.0 SDK 需要很长时间来实例化 VimService 类。(VimService类是运行'wsdl.exe vim.wsdl vimService.wsdl'生成的代理类)
In other words, the following line of code:
换句话说,以下代码行:
_service = new VimService();
Could take about 50 seconds to execute.
执行可能需要大约 50 秒。
CAUSE
原因
Apparently, the .NET XmlSerializer
uses the System.Xml.Serialization.*
attributes annotating the proxy classes to generate serialization code in run time. When the proxy classes are many and large, as is the code in VimService.cs, the generation of the serialization code can take a long time.
显然,.NETXmlSerializer
使用System.Xml.Serialization.*
注释代理类的属性在运行时生成序列化代码。当代理类很多并且很大时,如 VimService.cs 中的代码,序列化代码的生成可能需要很长时间。
SOLUTION
解决方案
This is a known problem with how the Microsoft .NET serializer works.
这是 Microsoft .NET 序列化程序工作方式的一个已知问题。
Here are some references that MSDN provides about solving this problem:
以下是 MSDN 提供的有关解决此问题的一些参考资料:
http://msdn2.microsoft.com/en-us/library/bk3w6240.aspxhttp://msdn2.microsoft.com/en-us/library/system.xml.serialization.xmlserializerassemblyattribute.aspx
http://msdn2.microsoft.com/en-us/library/bk3w6240.aspx http://msdn2.microsoft.com/en-us/library/system.xml.serialization.xmlserializerassemblyattribute.aspx
Unfortunately, none of the above references describe the complete solution to the problem. Instead they focus on how to pre-generate the XML serialization code.
不幸的是,以上参考文献都没有描述该问题的完整解决方案。相反,他们专注于如何预先生成 XML 序列化代码。
The complete fix involves the following steps:
完整的修复包括以下步骤:
Create an assembly (a DLL) with the pre-generated XML serializer code
Remove all references to System.Xml.Serialization.* attributes from the proxy code (i.e. from the VimService.cs file)
Annotate the main proxy class with the XmlSerializerAssemblyAttribute to point it to where the XML serializer assembly is.
使用预先生成的 XML 序列化程序代码创建程序集(DLL)
从代理代码中删除所有对 System.Xml.Serialization.* 属性的引用(即从 VimService.cs 文件中)
使用 XmlSerializerAssemblyAttribute 注释主代理类以将其指向 XML 序列化程序程序集所在的位置。
Skipping step 2 leads to only 20% improvement in the instantiation time for the VimService
class. Skipping either step 1 or 3 leads to incorrect code. With all three steps 98% improvement is achieved.
跳过第 2 步只会使VimService
类的实例化时间缩短 20% 。跳过第 1 步或第 3 步会导致代码不正确。通过所有三个步骤,实现了 98% 的改进。
Here are step-by-step instructions:
以下是分步说明:
Before you begin, makes sure you are using .NET verison 2.0 tools. This solution will not work with version 1.1 of .NET because the sgen tool and the XmlSerializationAssemblyAttribute
are only available in version 2.0 of .NET
在开始之前,请确保您使用的是 .NET verison 2.0 工具。此解决方案不适用于 .NET 1.1 版,因为 sgen 工具和XmlSerializationAssemblyAttribute
仅在 .NET 2.0 版中可用
Generate the VimService.cs file from the WSDL, using wsdl.exe:
wsdl.exe vim.wsdl vimService.wsdl
This will output the VimService.cs file in the current directory
Compile VimService.cs into a library
csc /t:library /out:VimService.dll VimService.cs
Use the sgen tool to pre-generate and compile the XML serializers:
sgen /p VimService.dll
This will output the VimService.XmlSerializers.dll in the current directory
Go back to the VimService.cs file and remove all
System.Xml.Serialization.*
attributes. Because the code code is large, the best way to achieve that is by using some regular expression substitution tool. Be careful as you do this because not all attributes appear on a line by themselves. Some are in-lined as part of a method declaration.If you find this step difficult, here is a simplified way of doing it:
Assuming you are writing C#, do a global replace on the following string:
[System.Xml.Serialization.XmlIncludeAttribute
and replace it with:
// [System.Xml.Serialization.XmlIncludeAttribute
This will get rid of the
Xml.Serialization
attributes that are the biggest culprits for the slowdown by commenting them out. If you are using some other .NET language, just modify the replaced string to be prefix-commented according to the syntax of that language. This simplified approach will get you most of the speedup that you can get. Removing the rest of the Xml.Serialization attributes only achieves an extra 0.2 sec speedup.Add the following attribute to the VimService class in VimService.cs:
[System.Xml.Serialization.XmlSerializerAssemblyAttribute(AssemblyName = "VimService.XmlSerializers")]
You should end up with something like this:
// ... Some code here ... [System.Xml.Serialization.XmlSerializerAssemblyAttribute(AssemblyName = "VimService.XmlSerializers")] public partial class VimService : System.Web.Services.Protocols.SoapHttpClientProtocol { // ... More code here
Regenerate VimSerice.dll library by
csc /t:library /out:VimService.dll VimService.cs
Now, from your application, you can add a reference to VimSerice.dll library.
Run your application and verify that VimService object instanciation time is reduced.
使用 wsdl.exe 从 WSDL 生成 VimService.cs 文件:
wsdl.exe vim.wsdl vimService.wsdl
这将输出当前目录中的 VimService.cs 文件
将 VimService.cs 编译成库
csc /t:library /out:VimService.dll VimService.cs
使用 sgen 工具预生成和编译 XML 序列化程序:
sgen /p VimService.dll
这将输出当前目录中的 VimService.XmlSerializers.dll
返回 VimService.cs 文件并删除所有
System.Xml.Serialization.*
属性。由于代码量很大,最好的方法是使用一些正则表达式替换工具。执行此操作时要小心,因为并非所有属性都单独出现在一行中。有些是作为方法声明的一部分内联的。如果你觉得这一步很困难,这里有一个简化的方法:
假设您正在编写 C#,请对以下字符串进行全局替换:
[System.Xml.Serialization.XmlIncludeAttribute
并将其替换为:
// [System.Xml.Serialization.XmlIncludeAttribute
这将
Xml.Serialization
通过注释掉那些是导致经济放缓的最大罪魁祸首的属性。如果您使用的是其他 .NET 语言,只需根据该语言的语法将替换的字符串修改为前缀注释。这种简化的方法将使您获得最大的加速。删除其余的 Xml.Serialization 属性只能实现额外 0.2 秒的加速。将以下属性添加到 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
重新生成 VimSerice.dll 库
csc /t:library /out:VimService.dll VimService.cs
现在,在您的应用程序中,您可以添加对 VimSerice.dll 库的引用。
运行您的应用程序并验证 VimService 对象实例化时间是否减少。
ADDITIONAL NOTES
补充说明
The sgen tool is a bit of a black box and its behavior varies depending on what you have in your Machine.config file. For example, by default it is supposed to ouptut optimized non-debug code, but that is not always the case. To get some visibility into the tool, use the /k flag in step 3, which will cause it to keep all its temporary generated files, including the source files and command line option files it generated.
sgen 工具有点像一个黑匣子,它的行为取决于您在 Machine.config 文件中的内容。例如,默认情况下它应该输出优化的非调试代码,但情况并非总是如此。要了解该工具,请在步骤 3 中使用 /k 标志,这将使其保留所有临时生成的文件,包括它生成的源文件和命令行选项文件。
Even after the above fix the time it takes to instantiate the VimService class for the first time is not instantaneous (1.5 sec). Based on empirical observation, it appears that the majority of the remaining time is due to processing the SoapDocumentMethodAttribute
attributes. At this point it is unclear how this time can be reduced. The pre-generated XmlSerializer assembly does not account for the SOAP-related attributes, so these attributes need to remain in the code. The good news is that only the first instantiation of the VimService class for that app takes long. So if the extra 1.5 seconds are a problem, one could try to do a dummy instantiation of this class at the beginning of the application as a means to improve user experience of login time.
即使在上述修复之后,第一次实例化 VimService 类所需的时间也不是即时的(1.5 秒)。根据经验观察,剩余时间的大部分似乎是由于处理SoapDocumentMethodAttribute
属性。目前还不清楚如何减少这个时间。预先生成的 XmlSerializer 程序集不考虑与 SOAP 相关的属性,因此这些属性需要保留在代码中。好消息是,只有该应用程序的 VimService 类的第一次实例化需要很长时间。因此,如果额外的 1.5 秒有问题,可以尝试在应用程序开始时对此类进行虚拟实例化,以改善用户登录时间的体验。
回答by Alex Lyman
You might wish to look into the Sgen.exe
tool that comes with .NET. There's also a handy little thing in Visual Studio's C# project properties "Build" page, at the very bottom, called "Build serialization assembly" that automatically runs Sgen
for you.
您可能希望查看Sgen.exe
.NET 附带的工具。Visual Studio 的 C# 项目属性“Build”页面中还有一个方便的小东西,位于最底部,称为“构建序列化程序集”,它会自动Sgen
为您运行。
回答by Alex Lyman
I believe that this is not an SGEN issue. I have looked at the constructor code, and I see that it is doing a lot of reflection (based on the XmlIncludeAttribute on the class). It reflects on all of them, and can take a really long time.
我相信这不是 SGEN 的问题。我查看了构造函数代码,发现它进行了大量反射(基于类上的 XmlIncludeAttribute)。它反映了所有这些,并且可能需要很长时间。
回答by Alex Lyman
There is a pre-generated XmlSerializer assembly that comes with CRM. Check to see whether you have SdkTypeProxy.XmlSerializers.dll and SdkProxy.XmlSerializers.dll in the GAC.
CRM 附带了一个预先生成的 XmlSerializer 程序集。检查 GAC 中是否有 SdkTypeProxy.XmlSerializers.dll 和 SdkProxy.XmlSerializers.dll。
If you don't then that means that when you create the CrmService, .net will generate the XmlSerializer assembly which can take some time. Hope this helps
如果不这样做,则意味着当您创建 CrmService 时,.net 将生成 XmlSerializer 程序集,这可能需要一些时间。希望这可以帮助
回答by Adam Marshall
I came across this thread when trying to find out why my initial SoapHttpClientProtocol
calls were taking so long.
当我试图找出为什么我的初始SoapHttpClientProtocol
电话花费这么长时间时,我遇到了这个线程。
I found that setting the Proxy to null/Empty stopped the Proxy AutoDetect from occurring - This was taking up to 7 seconds on the initial call:
我发现将 Proxy 设置为 null/Empty 会阻止 Proxy AutoDetect 发生 - 这在初始调用时最多需要 7 秒:
this.Proxy = GlobalProxySelection.GetEmptyWebProxy();
回答by Dragan Jovanovi?
I have used above detailed answer as guide, and went a few steps forward, making a script to automate process. Script is made out of two files :
我已使用上述详细答案作为指导,并向前迈进了几步,制作了一个脚本来自动化流程。脚本由两个文件组成:
generateproxy.bat :
生成代理.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.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
生成代理.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}"
I have added those two files to client project, and in the pre-build event I have added lines
我已将这两个文件添加到客户端项目中,并在预构建事件中添加了几行
cd..\..
generateproxy
So, before every build, proxy classes are regenerated, and developer has (almost) no need to think about it. While building, WS must be up and running, and its URL must be in bat file. As a result of prebuild, two dll files will regenerate in client project's subfolder references. After first execution of scripts, you should add reference to new dll.
因此,在每次构建之前,都会重新生成代理类,开发人员(几乎)不需要考虑它。构建时,WS 必须启动并运行,并且其 URL 必须在 bat 文件中。作为预构建的结果,两个 dll 文件将在客户端项目的子文件夹引用中重新生成。第一次执行脚本后,您应该添加对新 dll 的引用。