MergedDictionaries和资源查找

24
我在使用资源字典和合并字典的过程中遇到了问题,特别是在通过资源查找时的性能方面。经过一些性能测试,我发现ResourceDictionary.get_MergedDictionaries调用次数最多(在ANTS profiler中检查)。我们有大约300个资源字典XAML文件,并且其中很多都在使用合并字典来“包含”其他样式。其中一个应用程序部分,没有太多活动,get_MergedDictionaries的数量达到了大约1000万次。因此,我的猜测是我们在使用资源字典方面做错了什么。因此,我试图重构所有内容,并尝试摆脱所有合并字典。

现在是实际问题。我试图摆脱合并字典,但是失败了。我理解使用StaticResource时查找需要在当前资源之前定义资源。我制作了以下简短的示例:

一个主项目和一个自定义控件库。

该自定义控件库包含2个XAML文件。

<!-- Colors.xaml -->
<ResourceDictionary [stripped namespaces] >
    <SolidColorBrush x:Key="myColor" Color="Green"/>
</ResourceDictionary>

<!-- Templates.xaml -->
<ResourceDictionary [stripped namespaces]>
    <ControlTemplate x:Key="myTemplate" TargetType="Button">
        <Rectangle Fill="{StaticResource myColor}"/>
    </ControlTemplate>
</ResourceDictionary>

现在在主项目中,MainWindow.xaml 看起来像这样

<Window x:Class="ResourceTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/ResourceTestLib;component/Themes/Colors.xaml"/>
                <ResourceDictionary Source="/ResourceTestLib;component/Themes/Template.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <Button Template="{StaticResource myTemplate}"/>
    </Grid>
</Window>

这是我们期望的目标,但不幸的是,由于找不到资源“ myColor”,所以程序崩溃了。当然,我知道如何解决它——在Templates.xaml中添加MergedDictionary并引用Colors.xaml。但我一直认为(其实从来没有仔细查过)资源是根据逻辑树和元素资源进行查找的。我的理解是:创建按钮,尝试查找模板......找到;尝试查找颜色,在自己的资源中未找到,向上搜索并使用窗口资源。

看起来我错了。因此,希望有人能给我一些提示。我们在 WPF 中大量使用,尽管如此,由于一些最初错误的行为,我们的性能非常糟糕,只是因为资源查找。

非常感谢您的帮助。 谢谢 Nico


使用DynamicResource替代StaticResource来设置MyColor有什么问题吗?最后我检查过,性能差异不足以证明不使用动态资源。 - Rachel
我的主要关注点是性能,每个性能提示都提到不要使用DynamicResource,甚至MSDN也是这样。我的示例源代码仅包含我们问题的最小示例。因此,我们讨论大约3000-4000次StaticResource调用。 - dowhilefor
为什么你有这么多?通常资源只会被加载一次,所以你的整个应用程序中只应该有一个对Colors.xamlTemplates.xaml的引用。通常我会将通用资源(颜色、模板、样式等)加载到Application.Resources中,而像DataTemplates这样的特定资源则会被加载到使用它们的UserControl中。 - Rachel
顺便提一下,我发现Blend几乎完全使用DynamicResources编写,并且它生成的代码始终使用DynamicResource。如果静态资源和动态资源之间存在显着的性能差异,我认为他们不会这样做。 - Rachel
1
@Rachel 就像我在第一篇帖子中写的那样。我一开始就学错了或者理解错了。我们有很多自定义控件,每个自定义控件都有自己的xaml和样式。如果它是一个项控件,我们还有一个容器的xaml。我们还进一步拆分了xamls,这样设计师就可以只修改与设计相关的内容,而控件的主要xaml只引用设计师的东西。我们这样做是为了让设计师不必处理绑定和其他代码相关的事情。看起来这种旨在清晰结构的方法却使情况变得更糟。 - dowhilefor
4个回答

54

好的,我不喜欢回答自己的问题,但我想很多人可能会偶然遇到这个问题,并且我希望给他们提供我们当前的解决方案作为考虑的选项。

就像之前所说,我们有很多XAML文件,大约300个,用于各种不同的事物,如共享资源(画刷、颜色),但也包括许多包含不同DataTemplates、控件样式和自定义控件的XAML文件。在开始时,我们这种使用大量XAML文件的方法对我们来说是合理的,因为我们对类也是这样处理,使它们小而有组织。 不幸的是,WPF并不赞成这种方式。您拥有的ResourceDictionaries越多,通过MergedDictionaries合并的次数越多,性能就会越差。 我能给出的最好建议是,尽可能使用尽可能少的ResourceDictionary XAML文件

我们下定决心将其中许多XAML文件合并为一个巨大的XAML文件,事实上,我们现在使用预编译器将两个世界的优点结合起来。我们可以使用尽可能多的XAML文件,只需遵循一些限制,并在编译时将它们合并为一个巨大的XAML文件。我们获得的性能提升是显着的。在我的问题中,我写道“getMergedDictionaries有1100万个访问”……只需要“预编译”我们的一个程序集,我们就将其降至200万个访问,并且整个应用程序的性能始终要好得多。

因此,最终,XAML资源不应被视为编译的源代码,而应该被理解为实际的资源,当声明它时,它存在并且会占用空间和性能。

好吧,我们必须通过艰难的方式学习这一点。我希望每个人读到这篇文章都可以从我们的错误中学习,改进他们的项目。


你不会愿意分享你的预编译细节,或者至少指引我正确的方向吗? - gregsdennis
不幸的是,我不能分享代码,因为这些都是我工作的公司的代码。但这并不难。只需分析你的 XAML 文件中的合并字典,通过这些信息将每个文件关联起来,以了解需要以何种顺序进行合并。然后创建一个新的 XAML,并按照该顺序将每个 XAML 写入其中。我必须重新命名所有原始 XAML 文件,以便最终只剩下一个真正的 *.xaml 文件。我把所有的原始 XAML 文件称为 *.tamls。当然,您还需要处理名称相同的资源和命名空间和别名。但这也可以轻松解决。 - dowhilefor
你运行的是作为预构建步骤的控制台应用程序,还是与VS本身有关的东西? - gregsdennis
不,我只是建立了一个非常简单的控制台应用程序,并将其设置为预构建步骤,就像你说的那样。它有多种模式,因为我第一次需要准备项目时,需要重命名所有的XAML文件,以便它们不会被VS编译,我只是将它们转换为简单的资源。之后每次编译,都会获取所有所谓的TAML文件,解析它们,解决名称空间并将它们写入一个巨大的XAML文件中,然后由VS在编译时使用。 - dowhilefor
11
我遇到了类似的问题,并创建了一个预构建事件程序。您可以在GitHub上找到它。它包括一个示例应用程序,您可以看到所有 XAML 文件被合并为一个长的 XAML 文件。 我在我的博客文章中写了详细的说明。祝你开心 :) - Michael_S_

3
我倾向于在应用程序中使用一个ResourceDictionary,以避免任何性能问题。
为了使XAML易于管理,我使用XAML区域Visual Studio插件,并将每个资源类别包装在一个区域中。
- Brushes(画刷) - Text Styles(文本样式) - 等等...
对于这种情况,该插件是绝对的救星。 http://visualstudiogallery.msdn.microsoft.com/3c534623-bb05-417f-afc0-c9e26bf0e177

我已经在使用那个了。谢谢。组织不是问题。VS非常慢(指按键响应速度,而不仅仅是显示智能感知窗口)。我尝试过禁用Resharper,这似乎可以提高性能,但我不知道是否可以仅针对该文件(或XAML文件)禁用它,并且我不想失去那些强大的工具,只是为了编辑一个文本文件。 - gregsdennis

2

5
我们已经在使用它们,虽然我们没有获得很大的速度提升,但我们节省了很多内存。但是Sharedresource Dictionaries存在内存泄漏问题,我也在这里发布了链接 - dowhilefor

0

资源查找过程有没有得到解释?为什么找不到“myColor”?

顺便说一下,我找到了一种方法使其工作 - 但是这是一种奇怪而不稳定的方式。如果Application.xaml有这段代码,就可以找到颜色:

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="/ResourceTestLib;component/Themes/Colors.xaml"/>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="/ResourceTestLib;component/Themes/Template.xaml"/> 
        </ResourceDictionary.MergedDictionaries> 
    </ResourceDictionary>
</ResourceDictionary.MergedDictionaries>

然而,如果你将这段代码包含在另一个XAML文件中,再将其包含在Application.xaml中,即使资源结构相同(经由Snoop验证),也无法正常工作。


1
据我所知,资源将查找MyColor,在其当前的xaml中,其当前的MergedDictionaries中,在Generic.xaml(在某些情况下)和app.xaml及其MergedDictionaries中。这样它就可以正常工作。我们现在所做的是,删除所有合并的字典,将许多xmall xamls合并为一个xaml,并在app.xaml中将它们全部合并。因此,app.xaml是唯一允许具有合并字典的文件。您必须特别注意app.xaml中“包括”的顺序,但是记住这点,它运行得非常顺畅。 - dowhilefor

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