我使用的库使用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()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&amp;)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个类。
我假设所有这些类的加载一直占用,所以现在的问题是,它加载了什么类以及为什么?
更新
答案结果是因为只有一个AppDomain能够利用原生图像系统dll。所以第二个appdomain的缓慢是它必须重新调用wcf使用的所有System。* dll。第一个appdomain可以使用这些dll的预先定义的本机版本,因此它没有相同的启动成本。
在调查了Petar建议的LoaderOptimizationAttribute之后,确实似乎解决了这个问题,使用MultiDomain or MultiDomainHost导致第二个AppDomain导致第一次访问内容的时间相同WCF
在这里你可以看到默认选项,注意在第二个AppDomain中没有一个程序集说Native,这意味着它们都必须被重新安装,这就是所有的时间。
这是在将LoaderOptimization(LoaderOptimization.MultiDomain)添加到Main之后。您可以看到所有内容都已加载到共享AppDomain
中
这是用户LoaderOptimization(LoaderOptimization.MultiDomainHost)之后的main。您可以看到所有系统dll都是共享的,但是我自己的dll和GAC中没有的dll会单独加载到每个AppDomain中
因此,使用MultiDomainHost提示此问题的服务就是答案,因为它具有快速启动时间,我可以卸载AppDomains以删除服务使用的动态构建的程序集
答案 0 :(得分: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.
答案 1 :(得分:1)
您是否在IE中定义了HTTP代理? (也许是一个自动配置脚本)。这可能是一个原因。
否则我猜它是加载所有dll所需的时间。尝试将代理创建从actull调用到服务,以查看花费时间的内容。
答案 2 :(得分:1)
我发现following article讨论了第一个AppDomain如何才能使用本机映像dll,因此子appdomain将永远被强制JIT许多初始AppDomain不具备的东西。这可能会导致我看到的性能影响,但是有可能以某种方式不会对性能造成影响吗?
如果程序集有本机映像,则只有第一个AppDomain 可以使用原生图像。所有其他AppDomain都必须 JIT-编译可能导致显着CPU成本的代码。