【发布时间】:2011-10-15 00:01:58
【问题描述】:
如果您处理过一些较大的 wpf 应用程序,您可能熟悉 this。因为 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>
现在添加一个用户控件。代码隐藏只是默认设置,所以我只显示 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>
后面的Window代码是这样的
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
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>
我知道该程序并不完美,并且可能会变得更容易,但是在想办法显示问题的同时,这就是我想出的。无论如何:
启动它并检查内存消耗,如果您有内存分析器,这将变得容易得多。添加(通过单击选项卡显示它)并删除一个页面,您将看到一切正常。没有任何泄漏。
现在在UserControl.Resources 部分使用SharedResourceDictionary 而不是ResourceDictionary 来包含Shared.xaml。您会看到删除页面后MyUserControl 将保留在内存中,MyUserControl 在其中。
我认为这发生在所有通过 XAML 实例化的东西上,比如转换器、用户控件等。奇怪的是,这不会发生在自定义控件上。我的猜测是,因为没有真正在自定义控件、数据模板等上实例化。
那么首先我们如何避免这种情况?在我们的例子中,使用 SharedResourceDictionary 是必须的,但是内存泄漏使得它无法有效地使用它。 使用 CustomControls 而不是 UserControls 可以避免泄漏,但实际上并不总是这样。那么为什么 ResourceDictionary 会强引用 UserControls 呢? 我想知道为什么以前没有人经历过这种情况,就像我在一个较老的问题中所说的那样,似乎我们使用资源字典和 XAML 绝对是错误的,否则我想知道它们为什么如此低效。
我希望有人能对这件事有所了解。
提前致谢 妮可
【问题讨论】:
-
在您的内存分析器中,什么对象保留了对 MyUserControl 实例的引用?
-
我记不清了,但我几乎可以肯定它是一个 ResourceDictionary,在我的例子中是 SharedResourceDictionary。
标签: wpf memory-leaks resourcedictionary