Медленный конструктор SoapHttpClientProtocol - PullRequest
29 голосов
/ 05 октября 2008

Я провожу некоторые эксперименты с Microsoft Dynamics CRM. Вы взаимодействуете с ним через веб-сервисы, и я добавил веб-ссылку в свой проект. Интерфейс веб-службы очень богат, и сгенерированный «Reference.cs» имеет размер около 90 КБ.

Я использую веб-ссылку в консольном приложении. Я часто что-то меняю, перекомпилирую и запускаю. Компиляция выполняется быстро, но обновление ссылки на веб-сервис происходит очень медленно, это занимает около 15-20 секунд: CrmService service = new CrmService(); Профилирование показывает, что все время тратится в конструкторе SoapHttpClientProtocol.

Виновным является, по-видимому, тот факт, что код сериализации XML (не включенный в 90k loc, упомянутый выше) генерируется во время выполнения до JIT-обработки. Это происходит во время вызова конструктора. Ожидание довольно разочаровывает, когда вы играете и пробуете что-то новое.

Я пробовал различные комбинации sgen.exe, ngen и XGenPlus (это занимает несколько часов и генерирует 500 МБ дополнительного кода), но безрезультатно. Я подумал о реализации службы Windows, в которой есть несколько экземпляров CrmService, готовых к работе в случае необходимости, но это кажется чрезмерным.

Есть идеи?

Ответы [ 6 ]

36 голосов
/ 08 июня 2009

Следующее извлекается из этой ветки на форумах VMWare:

Привет, ребята,

Мы обнаружили, что sgen.exe работает. Просто есть несколько дополнительных шагов помимо предварительной генерации dll-файлов сериализатора, которые мы пропустили в этой теме. Вот подробная инструкция

ПРОБЛЕМА

При использовании VIM 2.0 SDK из .NET требуется длительное время для создания экземпляра класса 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. Создание сборки (DLL) с предварительно сгенерированным кодом XML-сериализатора

  2. Удалить все ссылки на атрибуты System.Xml.Serialization. * Из прокси-кода (то есть из файла VimService.cs)

  3. Добавьте основной прокси-класс с помощью атрибута XmlSerializerAssemblyAttribute, чтобы указать, где находится сборка XML-сериализатора.

Пропуск шага 2 приводит только к 20% улучшению времени создания класса VimService. Пропуск шага 1 или 3 приводит к неправильному коду. За все три этапа достигается улучшение на 98%.

Вот пошаговые инструкции:

Прежде чем начать, убедитесь, что вы используете инструменты .NET версии 2.0. Это решение не будет работать с версией 1.1 .NET, поскольку инструмент sgen и XmlSerializationAssemblyAttribute доступны только в версии 2.0 .NET

  1. Создайте файл VimService.cs из WSDL, используя wsdl.exe:

    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 в VimService.cs:

    [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. Например, по умолчанию предполагается отключить оптимизированный неотладочный код, но это не всегда так. Чтобы получить некоторое представление об инструменте, используйте флаг / k на шаге 3, который заставит его сохранить все свои временные сгенерированные файлы, включая исходные файлы и файлы параметров командной строки, которые он сгенерировал.

Даже после вышеуказанного исправления время, необходимое для создания экземпляра класса VimService в первый раз, не является мгновенным (1,5 секунды). На основании эмпирических наблюдений выясняется, что большая часть оставшегося времени связана с обработкой атрибутов SoapDocumentMethodAttribute. На данный момент неясно, как это время может быть сокращено. Предварительно сгенерированная сборка XmlSerializer не учитывает атрибуты, связанные с SOAP, поэтому эти атрибуты должны оставаться в коде. Хорошей новостью является то, что только первое создание экземпляра класса VimService для этого приложения занимает много времени. Таким образом, если дополнительные 1,5 секунды являются проблемой, можно попытаться создать фиктивную реализацию этого класса в начале приложения в качестве средства улучшения пользовательского опыта во время входа в систему.

1 голос
/ 18 декабря 2008

Существует предварительно сгенерированная сборка XmlSerializer, которая поставляется с CRM. Проверьте, есть ли в GAC файлы SdkTypeProxy.XmlSerializers.dll и SdkProxy.XmlSerializers.dll.

Если этого не сделать, это означает, что при создании CrmService .net сгенерирует сборку XmlSerializer, что может занять некоторое время. Надеюсь, это поможет

1 голос
/ 18 ноября 2008

Я считаю, что это не проблема SGEN. Я посмотрел на код конструктора и вижу, что он много размышляет (основан на XmlIncludeAttribute в классе). Это отражается на всех них и может занять очень много времени.

1 голос
/ 05 октября 2008

Возможно, вы захотите взглянуть на инструмент Sgen.exe, который поставляется с .NET. Есть также небольшая полезная вещь на странице «Сборка» свойств проекта Visual Studio C #, в самом низу, называемая «Сборка сборки сериализации», которая автоматически запускает Sgen для вас.

0 голосов
/ 09 сентября 2016

Я использовал приведенный выше подробный ответ в качестве руководства и сделал несколько шагов вперед, создав сценарий для автоматизации процесса. Скрипт состоит из двух файлов:

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 будут восстановлены в подпапке клиентского проекта ссылки . После первого выполнения скриптов, вы должны добавить ссылку на новую DLL.

0 голосов
/ 07 декабря 2012

Я наткнулся на эту ветку, когда пытался выяснить, почему мои первоначальные SoapHttpClientProtocol звонки были такими длинными.

Я обнаружил, что установка прокси-сервера на ноль / пустое состояние остановила автоматическое обнаружение прокси - это заняло до 7 секунд при первоначальном вызове:

this.Proxy = GlobalProxySelection.GetEmptyWebProxy();
...