Утечка памяти при использовании SharedResourceDictionary - PullRequest
11 голосов
/ 28 июля 2011

если вы работали над некоторыми более крупными wpf-приложениями, вы, возможно, знакомы с this .Поскольку ResourceDictionaries всегда создаются, каждый раз, когда они находятся в XAML, мы можем в конечном итоге иметь один словарь ресурсов несколько раз в памяти.Таким образом, вышеупомянутое решение кажется очень хорошей альтернативой.Фактически для нашего текущего проекта этот трюк многое сделал ... Потребление памяти от 800 Мб до 44 Мб, что очень сильно сказывается.К сожалению, это решение стоит денег, которые я хотел бы показать здесь, и, надеюсь, найти способ избежать этого, все еще используя 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 вот так

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, что не всегда практически.Так почему же на UserControls сильно ссылается ResourceDictionary?Я удивляюсь, почему никто не сталкивался с этим раньше, как я сказал в старом вопросе, кажется, что мы используем словари ресурсов и XAML абсолютно неправильно, в противном случае я удивляюсь, почему они так неэффективны.

Я надеюсь, что кто-то может пролить светпо этому вопросу.

Заранее спасибо Нико

Ответы [ 2 ]

12 голосов
/ 21 ноября 2012

Я столкнулся с той же проблемой необходимости каталогов общих ресурсов в крупномасштабном проекте WPF. Читая исходную статью и комментарии, я включил пару исправлений в класс SharedDirectory, как это предлагается в комментариях, которые, по-видимому, удалили строгую ссылку (хранится в _sourceUri), а также заставили дизайнер работать правильно. Я проверил ваш пример, и он работает, как в конструкторе, так и в MemProfiler, и не заметил никаких ссылок. Я хотел бы знать, если бы кто-то улучшил это далее, но это то, что я собираюсь сделать сейчас:

public class SharedResourceDictionary : ResourceDictionary
{
    /// <summary>
    /// Internal cache of loaded dictionaries 
    /// </summary>
    public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
        new Dictionary<Uri, ResourceDictionary>();

    /// <summary>
    /// Local member of the source uri
    /// </summary>
    private Uri _sourceUri;

    /// <summary>
    /// Gets or sets the uniform resource identifier (URI) to load resources from.
    /// </summary>
    public new Uri Source
    {
        get {
            if (IsInDesignMode)
                return base.Source;
            return _sourceUri;
        }
        set
        {
            if (IsInDesignMode)
            {
                try
                {
                    _sourceUri = new Uri(value.OriginalString);
                }
                catch
                {
                    // do nothing?
                }

                return;
            }

            try
            {
                _sourceUri = new Uri(value.OriginalString);
            }
            catch
            {
                // do nothing?
            }

            if (!_sharedDictionaries.ContainsKey(value))
            {
                // If the dictionary is not yet loaded, load it by setting
                // the source of the base class

                base.Source = value;

                // add it to the cache
                _sharedDictionaries.Add(value, this);
            }
            else
            {
                // If the dictionary is already loaded, get it from the cache
                MergedDictionaries.Add(_sharedDictionaries[value]);
            }
        }
    }

    private static bool IsInDesignMode
    {
        get
        {
            return (bool)DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty,
            typeof(DependencyObject)).Metadata.DefaultValue;
        }
    } 
}
7 голосов
/ 28 июля 2011

Я не совсем уверен, решит ли это вашу проблему. Но у меня были похожие проблемы с ResourceDictionary ссылками на элементы управления, и это связано с ленивой гидратацией . Вот сообщение на нем. И этот код решил мои проблемы:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        WalkDictionary(this.Resources);

        base.OnStartup(e);
    }

    private static void WalkDictionary(ResourceDictionary resources)
    {
        foreach (DictionaryEntry entry in resources)
        {
        }

        foreach (ResourceDictionary rd in resources.MergedDictionaries)
            WalkDictionary(rd);
    }
}
...