У меня есть библиотека, которую я использую, которая использует WCF для вызова http-сервиса для получения настроек. Обычно первый вызов занимает ~ 100 миллисекунд, а последующие вызовы занимают всего несколько миллисекунд. Но я обнаружил, что когда я создаю новый AppDomain, первый вызов WCF из этого AppDomain занимает более 2,5 секунд.
Есть ли у кого-нибудь объяснение или причина, почему первое создание канала WCF в новом AppDomain займет так много времени?
Это результаты тестов (при запуске без отладчика, подключенного к релизу на 64-битной основе), обратите внимание, как во втором наборе чисел первые соединения занимают более 25 раз больше
Running in initial AppDomain
First Connection: 92.5018 ms
Second Connection: 2.6393 ms
Running in new AppDomain
First Connection: 2457.8653 ms
Second Connection: 4.2627 ms
Это не полный пример, но показывает большую часть того, как я произвел эти числа:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Running in initial AppDomain");
new DomainRunner().Run();
Console.WriteLine();
Console.WriteLine("Running in new thread and AppDomain");
DomainRunner.RunInNewAppDomain("test");
Console.ReadLine();
}
}
class DomainRunner : MarshalByRefObject
{
public static void RunInNewAppDomain(string runnerName)
{
var newAppDomain = AppDomain.CreateDomain(runnerName);
var runnerProxy = (DomainRunner)newAppDomain.CreateInstanceAndUnwrap(typeof(DomainRunner).Assembly.FullName, typeof(DomainRunner).FullName);
runnerProxy.Run();
}
public void Run()
{
AppServSettings.InitSettingLevel(SettingLevel.Production);
var test = string.Empty;
var sw = Stopwatch.StartNew();
test += AppServSettings.ServiceBaseUrlBatch;
Console.WriteLine("First Connection: {0}", sw.Elapsed.TotalMilliseconds);
sw = Stopwatch.StartNew();
test += AppServSettings.ServiceBaseUrlBatch;
Console.WriteLine("Second Connection: {0}", sw.Elapsed.TotalMilliseconds);
}
}
Вызов приложения AppServSettings.ServiceBaseUrlBatch создает канал для службы и вызывает один метод. Я использовал wirehark для просмотра вызова, и для получения ответа от службы требуется миллисекунды. Он создает канал со следующим кодом:
public static ISettingsChannel GetClient()
{
EndpointAddress address = new EndpointAddress(SETTINGS_SERVICE_URL);
BasicHttpBinding binding = new BasicHttpBinding
{
MaxReceivedMessageSize = 1024,
OpenTimeout = TimeSpan.FromSeconds(2),
SendTimeout = TimeSpan.FromSeconds(5),
ReceiveTimeout = TimeSpan.FromSeconds(5),
ReaderQuotas = { MaxStringContentLength = 1024},
UseDefaultWebProxy = false,
};
cf = new ChannelFactory<ISettingsChannel>(binding, address);
return cf.CreateChannel();
}
Из профилирования приложения видно, что в первом случае создание канала factory и создание канала и вызов метода занимает менее 100 миллисекунд
В новом AppDomain для построения канала factory потребовалось 763 миллисекунды, 521 миллисекунды для создания канала, 1,098 миллисекунды для вызова метода на интерфейсе.
TestSettingsRepoInAppDomain.DomainRunner.Run() 2,660.00 TestSettingsRepoInAppDomain.AppServSettings.get_ServiceBaseUrlBatch() 2,543.47 Tps.Core.Settings.Retriever.GetSetting(строка,!! 0,!! 0,!! 0) 2,542.66 Tps.Core.Settings.Retriever.TryGetSetting(string,!! 0 &) 2,522.03 Tps.Core.Settings.ServiceModel.WcfHelper.GetClient() 1,371.21 Tps.Core.Settings.ServiceModel.IClientChannelExtensions.CallWithRetry(класс System.ServiceModel.IClientChannel) 1,098.83
ИЗМЕНИТЬ
После использования perfmon с объектом .NET CLR Loading, я вижу, что когда он загружает второй AppDomain, он загружает больше классов в память, чем изначально. Первая плоская линия - это пауза, которую я вставил после первого appdomain, там было загружено 218 классов. Второй AppDomain вызывает загрузку 1,944 всех классов.
Я предполагаю, что он загружает все эти классы, которые занимают все время, так что теперь вопрос в том, какие классы загружаются и почему?
UPDATE
Ответ оказывается из-за того, что только один AppDomain может использовать собственные библиотеки DLL. Таким образом, медленность во втором appdomain заключалась в том, что он должен был перезагружать все системные. * Dll, используемые wcf. Первый appdomain мог использовать предварительно созданные родные версии этих DLL, поэтому он не имел одинаковых затрат на запуск.
После изучения LoaderOptimizationAttribute, который предположил Петар, это действительно помогло решить проблему, используя MultiDomain или MultiDomainHost приводит к тому, что второй AppDomain занимает такое же количество времени, как и первый раз, чтобы получить доступ к материалам по wcf
Здесь вы можете увидеть параметр по умолчанию, обратите внимание, как во втором AppDomain ни одна из сборников не говорит "Родной", что означает, что все они должны быть перезаписаны, что все время занимало
Вот после добавления LoaderOptimization (LoaderOptimization.MultiDomain) в Main. Вы можете видеть, что все загружено в общий доступ к AppDomain
Ниже приведено описание функции LoaderOptimization (LoaderOptimization.MultiDomainHost) для пользователя. Вы можете видеть, что все системные DLL файлы являются общими, но мои собственные DLL файлы и все, что отсутствует в GAC, загружаются отдельно в каждый AppDomain
Итак, для службы, которая вызвала этот вопрос с помощью MultiDomainHost, это ответ, потому что он имеет быстрое время запуска, и я могу разгрузить AppDomains для удаления динамически построенных сборок, которые служба использует