在新的AppDomain中创建的第一个WCF连接非常缓慢怎么办?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (22)

我有一个使用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

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

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的调用正在为服务创建一个通道并调用单个方法。我已经使用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();
}

从分析应用程序中可以看出,在第一种情况下,构建渠道工厂并创建渠道并调用该方法的时间不到100毫秒

在构建通道工厂的新AppDomain中花费了763毫秒,创建通道的时间为521毫秒,在界面上调用方法的时间为1,098毫秒。

TestSettingsRepoInAppDomain.DomainRunner.Run()2660.00 TestSettingsRepoInAppDomain.AppServSettings.get_ServiceBaseUrlBatch()2543.47 Tps.Core.Settings.Retriever.GetSetting(string,!! 0 0 !! !! 0)2542.66 Tps.Core.Settings.Retriever.TryGetSetting (string,!! 0)2522.03 Tps.Core.Settings.ServiceModel.WcfHelper.GetClient()1371.21 Tps.Core.Settings.ServiceModel.IClientChannelExtensions.CallWithRetry(class System.ServiceModel.IClientChannel)1098.83

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

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

因为只有一个AppDomain能够利用本机映像系统dll。所以第二个appdomain的缓慢是它不得不重新启动wcf使用的所有System。* dll。第一个应用程序域可以使用这些dll的早期本地版本,因此它没有相同的启动成本。

在调查了Petar建议的LoaderOptimizationAttribute之后,确实似乎解决了这个问题,在第二个AppDomain中使用MultiDomain或MultiDomainHost结果来获取与第一次访问wcf相同的时间量

在这里可以看到默认选项,请注意第二个AppDomain中的程序集没有一个说Native,这意味着它们都必须重新编译,这就是所有的时间

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

这里是用户LoaderOptimization(LoaderOptimization.MultiDomainHost)为main之后。可以看到所有系统DLL都是共享的,但我自己的dll和任何不在GAC中的文件分别加载到每个AppDomain中

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

提问于
用户回答回答于

你可以用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.
用户回答回答于

你有在IE中定义的HTTP代理吗?(也许是一个自动配置脚本)。这可能是一个原因。

否则我会猜想这是加载所有dll所需的时间。尝试将代理创建从对服务的实际调用中分离出来,以查看需要花费的时间。

扫码关注云+社区