【问题标题】:WPF: slow template instantiationWPF:慢模板实例化
【发布时间】:2011-08-13 00:29:06
【问题描述】:

我有一个 WPF 应用程序,但速度很慢。

不是渲染。首先,渲染很简单,其次,我用 WPF Performance Toolkit 看了一下 - 什么都没有。

在我自己的代码中不是。首先,单元测试运行很快,其次,如果我将所有 DataTemplate 替换为空白,一切运行很快。

到目前为止,看起来缓慢的部分是模板实例化。也就是说,当您启动应用程序并打开一些复杂的屏幕时,需要很多 时间。 “很多”是指“很多”。有时可能长达 3-5 秒 - 例如,当有一个包含 100 行的数据网格时。但是,当您转到另一个选项卡,然后返回同一屏幕时,它会快速打开(只要它的视图模型保持不变)。

这很烦人,不仅因为它很慢,而且因为我对此无能为力。如果我能控制慢速,我可能会显示一些“opening, please wait”消息或其他东西......

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

有什么想法吗?有什么经典错误吗?有什么建议吗?

【问题讨论】:

  • 您想知道并做出有根据的猜测问题是什么。不要怀疑。不要猜测,也不要指望 Performance Toolkit 会告诉您。 Here's how to find out.
  • @Mike:是的,我广泛使用这种原始采样技术,当性能普遍下降时,它对我有很大帮助。但在这种情况下,我根本没有足够的时间来停止执行,因为性能下降最多只会持续 3-5 秒。此外,在我设法阻止它的极少数情况下,它总是在 WPF 的内部停止。我也确实在追寻这条线索(在 ILSpy 的帮助下),但到目前为止,它看起来非常无望。因此,我的问题更多是关于我在 WPF 上做错了什么。
  • 对。它停在 WPF 的内部,但堆栈上有什么?堆栈上的每一行都对所花费的时间负责。我曾经有人说“哦,每次我停止它时,它都在某个迭代器中。这有什么好处?”答案是很好,现在只需查找堆栈,您就会发现问题所在。如果您确实猜到了问题所在,它会告诉您您是否正确,如果您错了,它会告诉您正确的问题是什么。
  • @Mike:栈顶是 WPF,一直到我的 Main() 方法。这就是 WPF 的工作原理:它看到它必须显示一些对象(通常是响应鼠标单击,这会激活一个选项卡),所以它进入字典以检索该对象类型的模板,然后实例化该模板.一切都发生了,而无需碰我自己的代码一次。我所做的只是用模板填充这些字典并创建显示的原始对象。但这发生在很久以前,在程序开始时。而且速度很快。
  • @Mike:只是我,还是你突然想知道并做出有根据的猜测?也许首先阅读一些关于 WPF 如何工作的东西不会有什么坏处?这里有一个提示:你有根据的猜测并不完全正确。 ;-)

标签: c# wpf performance templates


【解决方案1】:

我的经验来自于开发 WPF 思维导图应用程序NovaMind

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

我们通过反复试验来手动测试我们的应用程序,并删除了部分用户控件以查看究竟是什么罪魁祸首。

最后我们通过完全重写我们的控件解决了性能问题。我们还大大降低了视觉树的复杂性。在重写之前,我们最常用的用户控件之一,当用 Snoop 检查时,由 61 个不同的东西组成,现在只有 3 个。我们尽可能只根据需要将东西添加到可视化树中。 (正如您在 XAML 中所知道的,即使您将事物设置为折叠,也需要先创建它们)。 最后,我们不得不编写自己的富文本渲染控件,因为内置的 RichtextBox 速度非常慢,而且 RichtextBox 的可视化树非常复杂。

我不知道这是否适用于您的情况,但我建议您调查您的用户控件,看看它们是否复杂。也许你有可以修剪的东西。 低垂的果实将是很少可见或可以以懒惰的方式创建的部分。您可以在必要时从后面的代码中创建这些部分,而不是将它们放在 XAML 中。这应该对你有很大帮助。

如果可能的话,虚拟化是你的朋友。不幸的是,在我们的案例中,我们无法做到这一点。

【讨论】:

  • 我刚刚验证了可视化树的大小。显示大约需要 1 秒的屏幕总共包含 850 个视觉效果。这在你的经验中是否过分?多大的树是可以接受的?
  • 作为一般经验法则,将树中每 1000 个元素创建为应用程序启动时间增加 1 秒。 Optimizing Your XAML App for Performance
【解决方案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,但它有效。只是在这里发帖,这样人们就不会失去我在内存分析器等中失去的日子。

【讨论】:

    【解决方案3】:

    在您的数据模板中使用用户控制并不是一个坏主意,但如果您渴望性能,那么您应该考虑切换到更轻的控制。例如,让一个 UserControl 只托管一个 TextBox 是非常糟糕的主意,因为 UserControl 由 ContentControl 组成,ContentControl 托管 ContentPresenter 而 ContentPresenter 将托管 TextBox,因此如果您注意到您的 Visual Tree,它具有三个新的 UI 元素层。减少 Visual Tree 肯定会提高性能。

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

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

    更改主题或模板会比更改承载内容的元素慢。并让元素在其自己的通用资源字典中拥有默认模板。

    【讨论】:

      【解决方案4】:
      • 尝试将所有资源向上移动 只要他们会去,最好进入 app.xaml
      • 检查是否可以使用 StaticResource 而不是动态的,静态的 更快
      • 如果可能,请尝试使用依赖关系 虚拟机中的属性,尤其是如果 你一次有很多,或者如果 他们有很多属性。这将使 wpf 不必进行大量反射。

      【讨论】:

      • 1) 资源都在 app.xaml 中。 2) 我从不使用动态资源。只有静态的。 3)不能这样做:首先,依赖属性需要线程亲和性,其次,它们实际上比 POCO 属性慢很多。有关更多信息,请参阅本文:markusegger.com/blog/…
      • 意见在 #3 上有所不同,因为数据绑定依赖属性更快。 msdn.microsoft.com/en-us/library/bb613546.aspx 至于线程亲和性。是的,这是真的,但是你绑定到的控件无论如何都是线程关联的,所以你仍然必须处理它..哦,好吧,只是把它扔在那里:)
      • 另一个例子,也可以看相关文章链接blog.lexique-du-net.com/index.php?post/2010/02/24/…
      • 坦率地说,我不知道为什么 Antoine 先生的测试显示相反的性能图片。我自己的测试与艾格先生的完全一致。会不会是 Antoine 先生不小心测试了他的调试版本,它在每次更改通知时都会访问反射?无论如何,我在我的确切情况下进行了自己的比较,并且不支持 DependencyProperties。
      【解决方案5】:

      您提到您正在使用具有 100 行的 DataGrid。您的性能问题的一个可能罪魁祸首是您使用的任何数据网格都没有进行虚拟化,因此您的可视化树是巨大的。

      通常,WPF 屏幕中较长的启动时间指向较大的可视化树。

      我不确定您是在每行使用一个数据模板,还是使用一些绑定列的第 3 方网格,或者是什么 - 但可以说您有 8 个带有控件的列。根据您的网格/验证/等,这可能是一个包含 20-60 个项目的可视化树每行。如果您有一个组合框,那么下拉菜单中的每个项目也可以按行创建。

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

      1. 尽可能使用虚拟化控件。这意味着在列表控件中使用 virtualizingstackpanel,并确保您的 3rd 方控件也能正常工作(现在许多普通 WPF 控件默认情况下会这样做)
      2. 不要过度使用用户控件、复合控件等。添加深度会增加时间,而在数据模板或其他重复区域中添加额外的可视化树深度会快速增加。
      3. 如果一切都失败了,显示一个简单的屏幕并通过代码添加控件以提高感知性能

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-11
        相关资源
        最近更新 更多