使用 SharedResourceDictionary 时发生内存泄漏
如果您开发过一些较大的 wpf 应用程序,您可能会熟悉此。由于 ResourceDictionaries 始终被实例化,因此每次在 XAML 中找到它们时,我们最终可能会在内存中多次拥有一个资源字典。所以上面提到的解决方案似乎是一个非常好的选择。事实上对于我们当前的项目来说这个技巧做了很多...内存消耗从 800mb 下降到 44mb,这是一个非常巨大的影响。不幸的是,这个解决方案是有代价的,我想在这里展示这一点,并希望找到一种方法来避免它,同时仍然使用 SharedResourceDictionary
。
我做了一个小例子来可视化共享资源字典的问题。
只需创建一个简单的 WPF 应用程序即可。添加一项资源 Xaml
Shared.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="myBrush" Color="Yellow"/>
</ResourceDictionary>
现在添加一个 UserControl。代码隐藏只是默认的,所以我只显示 xaml
MyUserControl.xaml
<UserControl x:Class="Leak.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SharedResourceDictionary="clr-namespace:Articy.SharedResourceDictionary" Height="128" Width="128">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Leak;component/Shared.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Rectangle Fill="{StaticResource myBrush}"/>
</Grid>
</UserControl>
后面的窗口代码看起来像这样
Window1.xaml.cs
// [ ... ]
public Window1()
{
InitializeComponent();
myTabs.ItemsSource = mItems;
}
private ObservableCollection<string> mItems = new ObservableCollection<string>();
private void OnAdd(object aSender, RoutedEventArgs aE)
{
mItems.Add("Test");
}
private void OnRemove(object aSender, RoutedEventArgs aE)
{
mItems.RemoveAt(mItems.Count - 1);
}
窗口 xaml 像这样
<窗口1.xaml
<Window.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type System:String}">
<Leak:MyUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="Add" Click="OnAdd"/>
<Button Content="Remove" Click="OnRemove"/>
</StackPanel>
<TabControl x:Name="myTabs" ContentTemplate="{StaticResource myTemplate}">
</TabControl>
</DockPanel>
</Grid>
</Window>
我知道这个程序并不完美,可能可以变得更容易,但在找出一种方法来显示问题时,这就是我想出的。不管怎样:
启动这个并检查内存消耗,如果你有内存分析器,这会变得容易得多。添加(通过单击选项卡显示它)并删除页面,您将看到一切正常。没有任何泄漏。 现在,在 UserControl.Resources
部分中,使用 SharedResourceDictionary
而不是 ResourceDictionary
来包含 Shared.xaml。您将看到,在删除页面后,MyUserControl
将保留在内存中,并且其中的 MyUserControl
也将保留在内存中。
我认为这种情况会发生在通过 XAML 实例化的所有内容上,例如转换器、用户控件等。奇怪的是,这不会发生在自定义控件上。我的猜测是,因为自定义控件、数据模板等上没有真正实例化任何内容。
那么首先我们如何避免这种情况呢?在我们的例子中,必须使用 SharedResourceDictionary,但是内存泄漏使得无法有效地使用它。 使用 CustomControls 而不是 UserControls 可以避免泄漏,但这并不总是实际的。那么为什么 UserControls 被 ResourceDictionary 强引用呢? 我想知道为什么以前没有人经历过这个,就像我在一个旧问题中所说的那样,似乎我们使用资源字典和 XAML 绝对是错误的,否则我想知道为什么它们如此低效。
我希望有人能够对这个问题有所了解。
提前致谢 尼科
if you worked on some larger wpf applications you might be familiar with this. Because ResourceDictionaries are always instantiated, everytime they are found in an XAML we might end up having one resource dictionary multiple times in memory. So the above mentioned solution seems like a very good alternative. In fact for our current project this trick did a lot ... Memory consumption from 800mb down to 44mb, which is a really huge impact. Unfortunately this solution comes at a cost, which i would like to show here, and hopefully find a way to avoid it while still use the SharedResourceDictionary
.
I made a small example to visualize the problem with a shared resource dictionary.
Just create a simple WPF Application. Add one resource Xaml
Shared.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="myBrush" Color="Yellow"/>
</ResourceDictionary>
Now add a UserControl. The codebehind is just the default, so i just show the xaml
MyUserControl.xaml
<UserControl x:Class="Leak.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SharedResourceDictionary="clr-namespace:Articy.SharedResourceDictionary" Height="128" Width="128">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Leak;component/Shared.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Rectangle Fill="{StaticResource myBrush}"/>
</Grid>
</UserControl>
The Window code behind looks something like this
Window1.xaml.cs
// [ ... ]
public Window1()
{
InitializeComponent();
myTabs.ItemsSource = mItems;
}
private ObservableCollection<string> mItems = new ObservableCollection<string>();
private void OnAdd(object aSender, RoutedEventArgs aE)
{
mItems.Add("Test");
}
private void OnRemove(object aSender, RoutedEventArgs aE)
{
mItems.RemoveAt(mItems.Count - 1);
}
And the window xaml like this
Window1.xaml
<Window.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type System:String}">
<Leak:MyUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="Add" Click="OnAdd"/>
<Button Content="Remove" Click="OnRemove"/>
</StackPanel>
<TabControl x:Name="myTabs" ContentTemplate="{StaticResource myTemplate}">
</TabControl>
</DockPanel>
</Grid>
</Window>
I know the program is not perfect and propably could be made easier but while figuring out a way to show the problem this is what i came up with. Anyway:
Start this and you check the memory consumption, if you have a memory profiler this becomes much easier. Add (with showing it by clicking on the tab) and remove a page and you will see everything works fine. Nothing leaks.
Now in the UserControl.Resources
section use the SharedResourceDictionary
instead of the ResourceDictionary
to include the Shared.xaml. You will see that the MyUserControl
will be kept in memory after you removed a page, and the MyUserControl
in it.
I figured this happens to everything that is instantiated via XAML like converters, user controls etc. Strangely this won't happen to Custom controls. My guess is, because nothing is really instantiated on custom controls, data templates and so on.
So first how we can avoid that? In our case using SharedResourceDictionary is a must, but the memory leaks makes it impossible to use it productively.
The Leak can be avoided using CustomControls instead of UserControls, which is not always practically. So why are UserControls strong referenced by a ResourceDictionary?
I wonder why nobody experienced this before, like i said in an older question, it seems like we use resource dictionaries and XAML absolutely wrong, otherwise i wonder why they are so inefficent.
I hope somebody can shed some light on this matter.
Thanks in advance
Nico
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我遇到了同样的问题,在大型 WPF 项目中需要共享资源目录。阅读源文章和评论后,我按照评论中的建议对 SharedDirectory 类进行了一些修复,这似乎删除了强引用(存储在 _sourceUri 中),并且还使设计器能够正常工作。我测试了你的示例,它有效,在设计器和 MemProfiler 中都成功地注意到没有保留引用。我很想知道是否有人进一步改进了它,但这就是我现在要做的:
I'm running into the same issue of needing shared resource directories in a large-ish WPF project. Reading the source article and the comments, I incorporated a couple fixes to the SharedDirectory class as suggested in the comments, which seem to have removed the strong reference (stored in _sourceUri) and also make the designer work correctly. I tested your example and it works, both in the designer and MemProfiler successfully noting no held references. I'd love to know if anyone has improved it further, but this is what i'm going with for now:
我不太确定这是否能解决您的问题。但我对
ResourceDictionary
引用控件及其与惰性 水合作用。这是帖子就可以了。这段代码解决了我的问题:I am not quite sure if this will solve your issue. But I had similar issues with
ResourceDictionary
referencing controls and its to do with lazy hydration. Here is a post on it. And this code resolved my issues: