是什么让WPF应用程序启动缓慢?

And*_*ndy 13 wpf startup

我注意到WPF应用程序启动有时很慢.有人知道原因是元素初始化还是DLL加载或其他什么?

stu*_*rtd 18

下面的文本摘自这篇关于改进WPF应用程序启动时间的MSDN文章(编辑:现在合并到WPF应用程序启动时间)

应用程序启动时间

WPF应用程序启动所需的时间量可能会有很大差异.本主题描述了用于减少Windows Presentation Foundation(WPF)应用程序的感知和实际启动时间的各种技术.

了解Cold Startup和WarmStartup

当您的应用程序在系统重新启动后第一次启动时,或启动应用程序,关闭它,然后在很长一段时间后再次启动时,就会发生冷启动.当应用程序启动时,如果Windows内存管理器的备用列表中不存在所需的页面(代码,静态数据,注册表等),则会发生页面错误.需要磁盘访问才能将页面带入内存.

当主公共语言运行时(CLR)组件的大多数页面已经加载到内存中时,会发生热启动,从而节省了昂贵的磁盘访问时间.这就是托管应用程序第二次运行时启动速度更快的原因.

实现启动画面

如果在启动应用程序和显示第一个UI之间存在明显的,不可避免的延迟,请使用启动屏幕优化感知的启动时间.此方法几乎在用户启动应用程序后立即显示图像.当应用程序准备好显示其第一个UI时,启动屏幕会消失.从.NET Framework 3.5 SP1开始,您可以使用SplashScreen类来实现启动屏幕.有关更多信息,请参见如何:向WPF应用程序添加启动画面.

您还可以使用本机Win32图形实现自己的启动屏幕.在调用Run方法之前显示您的实现.

分析启动代码

确定冷启动缓慢的原因.磁盘I/O可能是负责任的,但情况并非总是如此.通常,应尽量减少外部资源的使用,例如网络,Web服务或磁盘.

在测试之前,请确认没有其他正在运行的应用程序或服务使用托管代码或WPF代码.

重启后立即启动WPF应用程序,并确定显示所需的时间.如果您的应用程序的所有后续启动(热启动)都要快得多,那么您的冷启动问题很可能是由I/O引起的.

如果您的应用程序的冷启动问题与I/O无关,则可能是您的应用程序执行了一些冗长的初始化或计算,等待某些事件完成,或者在启动时需要大量的JIT编译.以下部分更详细地描述了其中一些情况.

优化模块加载

使用Process Explorer(Procexp.exe)和Tlist.exe等工具确定应用程序加载的模块.命令Tlist <pid>显示进程加载的所有模块.

例如,如果您没有连接到Web并且看到加载了System.Web.dll,则应用程序中有一个模块引用此程序集.检查以确保必须参考.

如果您的应用程序有多个模块,请将它们合并到一个模块中.这种方法需要较少的CLR组装负载开销.较少的组件也意味着CLR保持较少的状态.

推迟初始化操作

考虑推迟初始化代码,直到呈现主应用程序窗口.

请注意,可以在类构造函数内执行初始化,如果初始化代码引用其他类,则可能会导致执行许多类构造函数的级联效果.

避免应用配置

考虑避免应用配置.例如,如果应用程序具有简单的配置要求并且具有严格的启动时间目标,则注册表项或简单的INI文件可能是更快的启动替代方案.

利用GAC

如果未在全局程序集缓存(GAC)中安装程序集,则由于强名称程序集的哈希验证和Ngen映像验证(如果计算机上的该程序集的本机映像可用)导致延迟.对于GAC中安装的所有程序集,将跳过强名称验证.有关更多信息,请参阅Gacutil.exe(全局程序集缓存工具).

使用Ngen.exe

考虑在您的应用程序上使用本机映像生成器(Ngen.exe).使用Ngen.exe意味着交换CPU消耗以获得更多磁盘访问权限,因为Ngen.exe生成的本机映像可能比MSIL映像更大.

要改善热启动时间,应始终在应用程序上使用Ngen.exe,因为这样可以避免应用程序代码的JIT编译的CPU成本.

在某些冷启动方案中,使用Ngen.exe也很有帮助.这是因为不必加载JIT编译器(mscorjit.dll).

同时使用Ngen和JIT模块会产生最坏的影响.这是因为必须加载mscorjit.dll,并且当JIT编译器处理您的代码时,必须在JIT编译器读取程序集的元数据时访问Ngen映像中的许多页面.

Ngen和ClickOnce

您计划部署应用程序的方式也会对加载时间产生影响.ClickOnce应用程序部署不支持Ngen.如果您决定将Ngen.exe用于您的应用程序,则必须使用其他部署机制,例如Windows Installer.

有关更多信息,请参阅Ngen.exe(本机映像生成器).

重新绑定和DLL地址冲突

如果您使用Ngen.exe,请注意在本机映像加载到内存时可能会发生变基.如果DLL未在其首选基址加载,因为已经分配了该地址范围,则Windows加载程序会将其加载到另一个地址,这可能是一个耗时的操作.

您可以使用虚拟地址转储(Vadump.exe)工具检查是否存在所有页面都是私有的模块.如果是这种情况,则模块可能已重新定位到不同的地址.因此,其页面无法共享.

有关如何设置基址的详细信息,请参阅Ngen.exe(本机映像生成器).

优化Authenticode

Authenticode验证会增加启动时间.必须使用证书颁发机构(CA)验证Authenticode签名的程序集.此验证可能非常耗时,因为它可能需要多次连接到网络才能下载当前的证书吊销列表.它还确保在到受信任根的路径上有完整的有效证书链.加载程序集时,这可能会转换为几秒钟的延迟.

请考虑在客户端计算机上安装CA证书,或者尽可能避免使用Authenticode.如果您知道您的申请不需要发布者证据,则无需支付签名验证费用.

从.NET Framework 3.5开始,有一个配置选项允许绕过Authenticode验证.为此,请将以下设置添加到app.exe.config文件中:

<configuration>
 <runtime>
    <generatePublisherEvidence enabled="false"/>
  </runtime>
</configuration>
Run Code Online (Sandbox Code Playgroud)

在Windows Vista上比较性能

Windows Vista中的内存管理器有一项名为SuperFetch的技术.SuperFetch随时间分析内存使用模式,以确定特定用户的最佳内存内容.它始终持续工作以维护该内容.

此方法与Windows XP中使用的预取技术不同,后者在不分析使用模式的情况下将数据预加载到内存中.随着时间的推移,如果用户经常在Windows Vista上使用WPF应用程序,则应用程序的冷启动时间可能会有所改善.

有效使用AppDomains

如果可能,将程序集加载到域中性代码区域,以确保在应用程序中创建的所有AppDomain中使用本机映像(如果存在).

为获得最佳性能,请通过减少跨域调用来实施高效的跨域通信.如果可能,请使用不带参数的调用或使用基本类型参​​数.

使用NeutralResourcesLanguage属性

使用NeutralResourcesLanguageAttributeResourceManager指定中性文化.这种方法避免了不成功的程序集查找.

使用BinaryFormatter类进行序列化

如果必须使用序列化,请使用BinaryFormatter类而不是XmlSerializer类.所述的BinaryFormatter类基类库(BCL)在mscorlib.dll程序集实现.所述的XmlSerializer在system.xml.dll的组件,这可能是一个额外的DLL加载来实现.

如果必须使用XmlSerializer类,则可以在预生成序列化程序集时获得更好的性能.

启动后配置ClickOnce以检查更新

如果您的应用程序使用ClickOnce,请通过配置ClickOnce以在应用程序启动后检查部署站点以获取更新,从而避免启动时的网络访问.

如果您使用XAML浏览器应用程序(XBAP)模型,请记住,即使XBAP已在ClickOnce缓存中,ClickOnce也会检查部署站点是否有更新.有关更多信息,请参阅ClickOnce安全性和部署.

将PresentationFontCache服务配置为自动启动

重新启动后运行的第一个WPF应用程序是PresentationFontCache服务.该服务缓存系统字体,改进字体访问并提高整体性能.启动服务会产生开销,在某些受控环境中,请考虑将服务配置为在系统重新启动时自动启动.

以编程方式设置数据绑定

而不是使用XAML以声明方式为主窗口设置DataContext,请考虑在OnActivated方法中以编程方式设置它.

  • 本文合并到官方WPF MSDN文档中 - http://msdn.microsoft.com/en-us/library/cc656914.aspx (2认同)