在新AppDomain中建立的第一个WCF连接非常慢

Bra*_*AGr 17 c# performance wcf appdomain c#-4.0

我有一个我使用的库,它使用WCF调用http服务来获取设置.通常,第一次调用需要约100毫秒,后续调用只需几毫秒.但是我发现当我创建一个新的AppDomain时,来自该AppDomain的第一个WCF调用需要2.5秒.

有没有人解释或解决为什么在新的AppDomain中首次创建WCF频道需要这么长时间?

这些是基准测试结果(当在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
Run Code Online (Sandbox Code Playgroud)

这不是一个完整的例子,但显示了我生成这些数字的大部分内容:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

对AppServSettings.ServiceBaseUrlBatch的调用是创建服务的通道并调用单个方法.我使用wireshark来观看呼叫,从服务获得响应只需要一毫秒.它使用以下代码创建通道:

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();
}
Run Code Online (Sandbox Code Playgroud)

通过分析应用程序,它显示在第一种情况下构建通道工厂并创建通道并调用该方法需要不到100毫秒

在新的AppDomain构建通道工厂花了763毫秒,创建通道521毫秒,在接口上调用方法1,098毫秒.

TestSettingsRepoInAppDomain.DomainRunner.Run()2,660.00 TestSettingsRepoInAppDomain.AppServSettings.get_ServiceBaseUrlBatch()2,543.47 Tps.Core.Settings.Retriever.GetSetting(string,!! 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(class System.ServiceModel.IClientChannel)1,098.83

编辑

在使用带有.NET CLR Loading对象的perfmon之后,我可以看到,当它加载第二个AppDomain时,它会将更多的类加载到内存中,而不是最初.第一个平面线是我在第一个appdomain之后放置的暂停,它加载了218个类.第二个AppDomain导致加载1,944个类.

我假设所有这些类的加载一直占用,所以现在的问题是,它加载了什么类,为什么?

在此输入图像描述

UPDATE

答案结果是因为只有一个AppDomain能够利用原生图像系统dll.所以第二个appdomain的缓慢是它必须重新调用wcf使用的所有System.*dll.第一个appdomain可以使用这些dll的预先定义的本机版本,因此它没有相同的启动成本.

在调查Petar建议的LoaderOptimizationAttribute之后,确实似乎解决了这个问题,使用MultiDomain或MultiDomainHost导致第二个AppDomain花费的时间与第一次通过wcf访问内容的时间相同

在这里你可以看到默认选项,注意在第二个AppDomain中如何没有任何程序集说Native,这意味着它们都必须被重新安装,这就是所有的时间

在此输入图像描述

这是在将LoaderOptimization(LoaderOptimization.MultiDomain)添加到Main之后.您可以看到所有内容都已加载到共享AppDomain中

在此输入图像描述

这是用户LoaderOptimization(LoaderOptimization.MultiDomainHost)之后的main.您可以看到所有系统dll都是共享的,但是我自己的dll和GAC中没有的dll会单独加载到每个AppDomain中

在此输入图像描述

因此,对于使用MultiDomainHost提示此问题的服务是答案,因为它具有快速启动时间,我可以卸载AppDomains以删除服务使用的动态构建的程序集

Pet*_*tin 9

您可以使用LoaderOptimization属性修饰Main,以告诉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.
Run Code Online (Sandbox Code Playgroud)