加载大量用户控件时出现性能问题

发布于 2024-10-11 05:01:16 字数 260 浏览 1 评论 0原文

我的应用程序正在将一堆相同的用户控件加载到 ScrollPanel 中。问题是,这非常慢。

分析器显示瓶颈是 Application.LoadComponent() 方法,该方法是从我的用户控件的构造函数内部调用的。此方法的文档说此方法加载 XAML 文件。

问题是,如何使用 BAML 代替 XAML?如何才能确保用户控件的 XAML 在创建新实例时不会被一次又一次地解析?有没有其他方法可以更快地加载我的用户控件?

My application is loading a bunch of the same user control into a ScrollPanel. The problem is, this is very slow.

The profiler shows that the bottleneck is the method Application.LoadComponent(), which is called internally from the constructor of my user control. The documentation of this method says that this method loads XAML files.

The question is, how can I use BAML instead of XAML? How can I accomplish that the XAML for my user control must not be parse again and again when creating new instances from it? Is there another way to make loading my user controls faster?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

若相惜即相离 2024-10-18 05:01:17

LoadComponent() 已经加载了 .baml,不用担心这一点问题。 Microsoft 故意这样做,是为了不让开发人员依赖 baml 格式。展望未来,他们可以在不破坏任何现有应用程序的情况下改进格式。

是的,还有其他方法可以让它运行得更快。第一个优化是UI 虚拟化。 WPF 已经附带了方便的 VirtualizingStackPanel。它与 ItemsControls 协同工作,并且有一定的限制(例如,如果您创建项目容器并自行添加它们,则会失去虚拟化,或者如果设置 ScrollViewer.CanContentScroll="False" 则会失去虚拟化再次)。要使用虚拟化,您可能需要重写应用程序以使用 ItemsControl + DataBinding 样式(ListBox 已启用虚拟化)

如果您觉得需要有关 UI 虚拟化的更多信息请参阅 Dan Crevier 的博客

以及最后的建议。您可以尝试将用户控件重写为自定义控件。我的简单性能测试显示如下图。使用相同的可视化树创建 10K 控件所花费的时间:

  • 对于 UserControl:4932ms
  • 对于 CustomControl:86ms;(快约 57 倍)

希望这有帮助

LoadComponent() already loads .baml, don't worry about this bit of the question. Microsoft did this intentionally, to not let developers make dependencies on baml format. Going forward they can improve the format without breaking any existing applications.

Yes, there are another ways of making it work faster. The first optimization to take is UI virtualization. WPF already comes with handy VirtualizingStackPanel. It works in tandem with ItemsControls, and has certain limitations (e.g. if you create items containers and add them by your own, you loose virtualization, or if you set ScrollViewer.CanContentScroll="False" you loose it again). To use virtualization you will probably have to rewrite your application to use ItemsControl + DataBinding style (ListBox already has virtualization enabled)

If you feel like you need even more information on UI virtualization refer to Dan Crevier's blog.

And final advice. You can try rewriting your UserControls to Custom Controls. My simple performance test showed the following figures. To create 10K controls with the same visual tree it took:

  • for UserControl: 4932ms;
  • for CustomControl: 86ms; (~57 times faster)

Hope this helps

碍人泪离人颜 2024-10-18 05:01:17

当实例化用户控件时,调用链如下所示:

  • UserControl.Constructor
    • UserControl.InitializeComponent()
      • Application.LoadComponent()
        • XamlReader.LoadBAML()

根据我的经验,它的最终调用实际上一直消耗时间。这是加载 XAML 文件(以 BAML 格式存储)并将其转换为实际对象以供使用的步骤。

我发现了两种减少或消除此处所用时间的通用方法。


以下简单的重构很容易完成,在我处理过的情况下,加载时间可能会减少大约 75%(非常粗略!)通常您会开始使用如下内容:

典型的 UserControl起点

MyControl.xaml文件:

<UserControl x:Class="MyNamespace.MyControl" ... >

   <Grid ... >
     ...
   </Grid>

</UserControl>

对应MyControl.xaml.cs文件:

namespace MyNamespace
{
    partial class MyControl : UserControl
    {
        public MyControl()
        {
             InitializeComponent();

             // other stuff...
        }
    }
}

重构1:Control与xaml/xaml .cs 文件

从该起点开始,将其修改为从 Control 而不是 UserControl 派生,并显式设置 ControlTemplate

MyControl.xaml 文件:

<Control x:Class="MyNamespace.MyControl" ... >
 <Control.Template>
  <ControlTemplate>
    <Grid ... >
      ...
    </Grid>
  </ControlTemplate>
 </Control.Template>
</Control>

对应的 MyControl.xaml.cs 文件:

namespace MyNamespace
{
    partial class MyControl : Control
    {
        public MyControl()
        {
             InitializeComponent();

             // other stuff...
        }
    }
}

无论出于何种原因,在我尝试过的所有示例中,仅执行此操作就减少了大约 75%(大约)的加载时间。这是一种非常快速且简单的重构,具有显着的优势。

Visual Studio 对 xaml / xaml.cs 的使用非常满意,尽管它有点不标准。

但请注意,XAML 仍将在控件的每个实例化中加载和处理。不过,这是可以避免的,请参阅下一节。


重构 2:使用资源字典的 Control

就性能而言,更好的方法是将 ControlTemplate 重构为资源字典。这完全消除了每个控件实例的 XAML 负载,并且(根据我的经验)几乎没有延迟。

将原始 .xaml 文件替换为“MyControlResources.xaml”,如下所示:

<ResourceDictionary ... >

    <ControlTemplate x:Key="MyControlTemplateKey">
       <Grid ... >
           ...
       </Grid>
    </ControlTemplate>

    <Style TargetType="{x:Type MyNamespace:MyControl}">
        <Setter
            Property="Template"
            Value="{StaticResource MyControlTemplateKey}"
            />
    </Style>

</ResourceDictionary>

此资源字典需要添加到 app.xaml< 中的合并字典中/代码>。

将原始 xaml.cs 文件替换为纯 MyControl.cs 文件,其中包含:

namespace MyNamespace
{
    class MyControl : Control
    {
        public MyControl()
        {
             // other stuff...
        }
    }
}

请注意,不再调用 InitializeComponent()。样式系统用于应用XAML仅加载一次的控件模板。

这是一个更实质性的重构,但仍然相对容易执行,因为通常需要很少的其他代码修改。最初由 ElementName 直接对顶级控件进行的绑定应更改为使用 TemplateBinding

您可以组织控制模板和以其他方式样式资源,这只是显示了一种直接对应于用户控件的原始代码组织的方法。我个人喜欢一个控件对应一个资源字典。

When a user control is instantiated the call chain is something like this:

  • UserControl.Constructor
    • UserControl.InitializeComponent()
      • Application.LoadComponent()
        • XamlReader.LoadBAML()

In my experience its the final call which actually consumes all the time. That is the step where the XAML file (stored in BAML format) is loaded and converted into actual objects for use.

I have found two general approaches to reducing or eliminating the time taken here.


The following simple refactoring is easy to do and in the cases I've worked on may reduce load time by about 75% (very roughly!) Typically you would start out having something like this:

Typical UserControl starting point

MyControl.xaml file:

<UserControl x:Class="MyNamespace.MyControl" ... >

   <Grid ... >
     ...
   </Grid>

</UserControl>

Corresponding MyControl.xaml.cs file:

namespace MyNamespace
{
    partial class MyControl : UserControl
    {
        public MyControl()
        {
             InitializeComponent();

             // other stuff...
        }
    }
}

Refactoring 1: Control with xaml/xaml.cs files

From that starting point, modify it to derive from Control instead of UserControl and set the ControlTemplate explictly:

MyControl.xaml file:

<Control x:Class="MyNamespace.MyControl" ... >
 <Control.Template>
  <ControlTemplate>
    <Grid ... >
      ...
    </Grid>
  </ControlTemplate>
 </Control.Template>
</Control>

Corresponding MyControl.xaml.cs file:

namespace MyNamespace
{
    partial class MyControl : Control
    {
        public MyControl()
        {
             InitializeComponent();

             // other stuff...
        }
    }
}

For whatever reason in all the examples I had tried, just doing this knocked off about 75% (roughly) of the load time. This can be a very quick and easy refactoring that has a substantial advantage.

Visual Studio is perfectly happy with this xaml / xaml.cs usage even though its a little nonstandard.

However note that the XAML will still be loaded and processed in each instantiation of the control. This is avoidable however, see the next section.


Refactoring 2: Control with resource dictionary

Even better, in terms of performance, is to refactor the ControlTemplate into a resource dictionary. This totally eliminates the per-control instance XAML load and (in my experience) results in almost no delay at all.

Replace the original .xaml file with "MyControlResources.xaml" such as the following:

<ResourceDictionary ... >

    <ControlTemplate x:Key="MyControlTemplateKey">
       <Grid ... >
           ...
       </Grid>
    </ControlTemplate>

    <Style TargetType="{x:Type MyNamespace:MyControl}">
        <Setter
            Property="Template"
            Value="{StaticResource MyControlTemplateKey}"
            />
    </Style>

</ResourceDictionary>

This resource dictionary needs to be added to a merged dictionary in app.xaml.

Replace the original xaml.cs file with a plain MyControl.cs file containing:

namespace MyNamespace
{
    class MyControl : Control
    {
        public MyControl()
        {
             // other stuff...
        }
    }
}

Note that there is no longer any call to InitializeComponent(). The style system is used to apply the control template whose XAML is loaded only once.

This is a somewhat more substantial refactoring but still relatively easy to carry out as usually few other code modifications are needed. Bindings originally made by ElementName to the top level control directly should be changed to use TemplateBinding.

You could organize the control template & style resources in other ways, this just shows an approach which directly corresponds to the original organization of code for the user control. Personally I like to have one resource dictionary corresponding to one control.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文