Подтвердить что ты не робот

Первое соединение WCF, созданное в новом AppDomain, очень медленное

У меня есть библиотека, которую я использую, которая использует 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 всех классов.

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

enter image description here

UPDATE

Ответ оказывается из-за того, что только один AppDomain может использовать собственные библиотеки DLL. Таким образом, медленность во втором appdomain заключалась в том, что он должен был перезагружать все системные. * Dll, используемые wcf. Первый appdomain мог использовать предварительно созданные родные версии этих DLL, поэтому он не имел одинаковых затрат на запуск.

После изучения LoaderOptimizationAttribute, который предположил Петар, это действительно помогло решить проблему, используя MultiDomain или MultiDomainHost приводит к тому, что второй AppDomain занимает такое же количество времени, как и первый раз, чтобы получить доступ к материалам по wcf

Здесь вы можете увидеть параметр по умолчанию, обратите внимание, как во втором AppDomain ни одна из сборников не говорит "Родной", что означает, что все они должны быть перезаписаны, что все время занимало

enter image description here

Вот после добавления LoaderOptimization (LoaderOptimization.MultiDomain) в Main. Вы можете видеть, что все загружено в общий доступ к AppDomain

enter image description here

Ниже приведено описание функции LoaderOptimization (LoaderOptimization.MultiDomainHost) для пользователя. Вы можете видеть, что все системные DLL файлы являются общими, но мои собственные DLL файлы и все, что отсутствует в GAC, загружаются отдельно в каждый AppDomain

enter image description here

Итак, для службы, которая вызвала этот вопрос с помощью MultiDomainHost, это ответ, потому что он имеет быстрое время запуска, и я могу разгрузить AppDomains для удаления динамически построенных сборок, которые служба использует

4b9b3361

Ответ 1

Вы можете украсить свой Main с помощью LoaderOptimization, чтобы сообщить загрузчику CLR, как загружать классы.

[LoaderOptimization(LoaderOptimization.MultiDomain)]
MultiDomain - Indicates that the application will probably have many domains that use the same code, and the loader must share maximal internal resources across application domains.

Ответ 2

У вас есть прокси-сервер HTTP, определенный в IE? (возможно, для автоматической настройки script). Это может быть причиной.

В противном случае я бы догадался, что это время, необходимое для загрузки всех DLL. Попытайтесь отделить создание прокси от вызова actull к сервису, чтобы узнать, что требуется время.

Ответ 3

Я нашел следующую статью, в которой рассказывается о том, как только первый AppDomain может использовать DLL для родного образа, поэтому дочерний appdomain всегда будет вынужден JIT много вещей, которые нет в первоначальном AppDomain. Это может привести к влиянию на производительность, которое я вижу, но можно ли как-то не получить этого штрафа за производительность?

Если для сборки есть собственное изображение, только первый AppDomain может использовать собственное изображение. Все остальные AppDomains должны будут JIT-компилируйте код, который может привести к значительной стоимости процессора.