WPF:模板实例化缓慢

27

我有一个WPF应用程序,但是它运行得很慢。

首先,这与渲染无关。渲染非常简单,而且我使用了WPF性能工具包进行了检查 - 没有问题。

其次,这也不是我的代码的问题。单位测试很快,并且如果我将所有DataTemplates替换为空模板,则一切都可以快速运行。

到目前为止,看起来问题出在模板实例化上。也就是说,在启动应用程序并打开一些复杂的屏幕时,需要很长一段时间。当我说"很长一段时间"时,我的意思是"真的很长"。有时可能高达3-5秒 - 例如,在有100行的数据网格中。但是,当您转到另一个选项卡,然后返回到相同的屏幕时,它会快速打开(只要其视图模型保持不变)。

这非常令人恼火,不仅因为它运行缓慢,而且因为我无法解决它。如果我对速度有一些控制,我可能可以显示一些"正在打开,请稍候"之类的消息...

此外,当我查看其他一些WPF应用程序(尤其是ILSpy)时,它们似乎运行得相当快,尽管存在大量数据。这使我相信我可能做错了什么。但是我不知道从哪里开始。

有什么想法吗?有什么经典的错误吗?有什么提示吗?


你正在猜测问题所在并做出有根据的猜测。不要猜测,也不要期望性能工具包告诉你。以下是如何找到答案的方法。 - Mike Dunlavey
好的。 它停在 WPF 的内部,但是堆栈上有什么? 堆栈上的每一行都是那段时间的责任所在。 我曾经听人说:“哦,每次我停下来,它都在某个迭代器中。 那有什么用?” 答案是很好,现在只需要查找堆栈,就可以看到问题所在了。 如果您猜测问题所在,那将告诉您是否正确,如果错误,它将告诉您正确的问题是什么。 - Mike Dunlavey
@Mike:堆栈的最上层是WPF,一直到我的 Main() 方法。这就是WPF的工作原理:它发现需要显示某个对象(通常是响应鼠标点击而激活选项卡),然后在其字典中检索该对象类型的模板,并实例化该模板。所有这些都在不接触我的代码的情况下发生。我所做的一切只是填充这些字典并创建要显示的原始对象。但这已经发生得很早了,在程序的起始阶段。而且速度很快。 - Fyodor Soikin
以下是一些建议。这里有关于XAML调试器的信息。看起来WPF正在解释XAML,所以我会尝试检查足够的WPF状态,在暂停时查看它正在解释哪个XAML。 - Mike Dunlavey
3
@Mike: 是我自己的问题,还是你突然也开始猜测并做出合理的判断了?也许先读一些关于WPF工作原理的内容会有所帮助。这里有一个提示:你的合理猜测并不完全正确。;-) - Fyodor Soikin
显示剩余6条评论
5个回答

6

我的经验来自于在WPF思维导图应用程序NovaMind上的工作。

几个月前,我们完全重写了中间层以解决我们遇到的性能问题。简而言之,创建用户控件似乎太慢了。不幸的是,我找不到一个很好的方法来分析性能,因为无论是WPF性能套件还是商业应用程序(如ANTS Profiler)都没有提供关于WPF过程的这一部分的详细信息。(当时我问了这个问题

我们只能通过试错手动测试我们的应用程序,并删除我们的用户控件的部分,以查看到底是什么原因导致性能问题。

最终,我们通过完全重写我们的控件来解决了性能问题。我们还大大简化了我们的可视树的复杂性。在重写之前,我们最常用的一个用户控件,在使用Snoop进行检查时由61个不同的部分组成,现在只有3个。在可能的情况下,我们只在需要时将东西添加到可视树中。(正如您所知,在XAML中,即使将东西设置为Collapsed,它们也需要先被创建)。 最后,我们不得不编写自己的富文本呈现控件,因为内置的RichtextBox非常慢,而且RichtextBox的可视树相当复杂。

我不知道这是否适用于您的情况,但我建议您调查一下您的用户控件,看看它们是否很复杂。也许您有一些可以简化的部分。 低垂的果实可能是只在很少情况下可见或可以以惰性方式创建的部分。您可以在必要时从代码中创建这些部分,而不是将它们放在XAML中。这应该会对您有所帮助。

否则,如果可能,虚拟化是您的朋友。不幸的是,在我们的情况下,我们无法做到这一点。


我刚刚验证了可视树的大小。一个需要大约1秒钟才能显示的屏幕包含总共850个可视元素。在您的经验中,这是否过多?什么样的树大小是可以接受的? - Fyodor Soikin
作为一个经验法则,每创建1000个元素在树中,会增加1秒钟的应用启动时间。 - rschoenbach

2

这听起来与我曾经遇到的问题相似。我在这里发布了解决方法:WPF UI Automation issue。只是为了搜索者的利益而发布,因为花费了很长时间才解决。

针对链接回答的评论,这里是那篇文章的重点:

我做了以下事情:

  1. 下载热补丁 - - http://archive.msdn.microsoft.com/KB978520(可能不需要)
  2. 下载热补丁 - - http://archive.msdn.microsoft.com/KB2484841(即使您使用的是Windows 7 / .NET 4也绝对需要)
  3. 进一步改进了代码(验证导致了过多的对象)- Why does WPF Style to show validation errors in ToolTip work for a TextBox but fails for a ComboBox?

可能只需要第3步,但它有效。我在这里发布,以便人们不会像我一样在内存分析工具中浪费时间。


1

你提到正在使用一个包含100行的DataGrid。导致性能问题的罪魁祸首很可能是你使用的DataGrid没有进行虚拟化,因此你的可视树非常庞大。

通常,在WPF屏幕中启动时间长意味着可视树很大。

我不确定你是否在每一行使用了数据模板,或者使用了某些第三方网格来绑定列等等 - 但假设你有8个带控件的列。根据你的网格/验证等等,这可能是每行20-60个项目的可视树。如果你有一个下拉框,那么下拉列表中的每个项目也可能会被创建。

要解决这个问题只需要注意细节,并随时采取措施:

  1. 尽可能使用虚拟化控件。这意味着在列表控件内使用虚拟化堆栈面板,并确保你的第三方控件也这样做(许多默认情况下都是这样的)
  2. 不要过度使用UserControls、复合控件等等。增加深度会增加时间,在数据模板或其他重复区域中添加额外的可视树深度会快速累积。
  3. 如果所有其他方法都失败了,请显示一个简单的屏幕,并通过代码添加控件来提高感知性能。

1

在数据模板中使用用户控件并不是完全不好的想法,但如果你追求性能,那么你应该考虑切换到更轻量级的控件。例如,仅托管一个文本框的用户控件是非常糟糕的想法,因为用户控件由ContentControl组成,ContentControl托管ContentPresenter,而ContentPresenter将托管TextBox,所以如果你注意到你的可视树,它有三个新的UI元素层。减少可视树肯定会提高性能。

我建议创建自定义控件,这可能是一个全新的控件,具有与要呈现的数据相关的一些依赖属性,并且您可以在generic.xaml中拥有自己的自定义模板。其次,您可以从现有控件派生控件,并在generic.xaml中重新定义其默认模板。

这种方法肯定会更好,因为您将减少您的可视树,从而减少可视状态管理器的工作。

更改主题或模板比更改托管内容的元素慢。让元素在其自己的通用资源字典中具有默认模板。


1
  • 尝试将所有资源尽可能地移至app.xaml中
  • 检查是否可以使用StaticResource而不是动态资源,静态资源速度更快
  • 如果可能,请在您的VM中使用依赖属性,特别是如果一次有很多VM或者它们有很多属性。这将使wpf避免进行大量反射。

  1. 资源已经全部在app.xaml中了。
  2. 我从来不使用DynamicResource,只使用静态资源。
  3. 做不到这一点: 首先,依赖属性需要线程亲和性,其次,它们实际上比POCO属性慢得多。请参阅此文章以获取更多信息: http://www.markusegger.com/blog/development.aspx?messageid=3dfccfc5-1a30-4af1-a924-da22f3ff6057
- Fyodor Soikin
对于第三点,意见不一,对于数据绑定依赖属性更快。http://msdn.microsoft.com/en-us/library/bb613546.aspx 对于线程亲和性。是的,这是真的,但是您绑定到的控件无论如何都是线程亲和性的,因此您仍然必须处理它。哦,好吧,只是随便说说 :) - aL3891
另一个例子,还可以参见相关文章链接http://blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged - aL3891
1
坦白说,我不知道为什么安东先生的测试结果显示出了相反的性能情况。我的测试结果与埃格尔先生的完全一致。难道安东先生不小心测试了他的调试版本,该版本在每次更改通知时都会访问反射?无论如何,在我的确切情况下,我进行了自己的比较,结果并不支持依赖属性。 - Fyodor Soikin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接