Wyciek pamięci podczas korzystania z SharedResourceDictionary

Jeśli pracowałeś nad większymi aplikacjami wpf, możesz być zaznajomiony z tym . Ponieważ ResourceDictionaries są zawsze instancjonowane, za każdym razem, gdy zostaną znalezione w XAML, możemy mieć jeden słownik zasobów wiele razy w pamięci. Powyższe rozwiązanie wydaje się więc bardzo dobrą alternatywą. W rzeczywistości dla naszego obecnego projektu ta sztuczka zrobiła wiele ... Zużycie pamięci od 800MB w dół do 44MB, co jest naprawdę ogromny wpływ. Niestety takie rozwiązanie przychodzi za cenę, którą chciałbym tutaj pokazać, i mam nadzieję, że znajdę sposób, aby tego uniknąć, jednocześnie korzystając z SharedResourceDictionary.

Zrobiłem mały przykład, aby zwizualizować problem ze współdzielonym słownikiem zasobów.

Wystarczy utworzyć prostą aplikację WPF. Dodaj jeden zasób Xaml

Wspólne.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>

Teraz dodaj UserControl. Kod jest tylko domyślnym, więc po prostu pokazuję 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>

Kod okna 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);
    }

I okno xaml jak to

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>

Wiem, że program nie jest doskonały i prawdopodobnie mógłby być łatwiejszy, ale podczas wymyślania sposobu, aby pokazać problem, to jest to, co wymyśliłem. W każdym razie:

Uruchom to i sprawdzić zużycie pamięci, jeśli masz Profiler pamięci staje się znacznie łatwiejsze. Dodaj (z pokazaniem go klikając na tab) i usuń stronę, a zobaczysz, że wszystko działa dobrze. Nic nie przecieka. Teraz w sekcji UserControl.Resources Użyj SharedResourceDictionary zamiast ResourceDictionary, aby dołączyć Shared.xaml . Zobaczysz, że MyUserControl zostanie zachowana w pamięci po usunięciu strony, a MyUserControl w niej.

Domyśliłem się, że dzieje się tak ze wszystkim, co jest inicjowane przez XAML, jak konwertery, kontrolki użytkownika itp. O dziwo nie stanie się to w przypadku niestandardowych kontroli. Domyślam się, że nic nie jest tak naprawdę instancjonowane na niestandardowe kontrolki, szablony danych i tak dalej.

Więc najpierw jak możemy tego uniknąć? W naszym przypadku korzystanie z SharedResourceDictionary jest koniecznością, ale wycieki pamięci uniemożliwiają jego produktywne wykorzystanie. Wycieku można uniknąć za pomocą CustomControls zamiast UserControls, co nie zawsze jest praktycznie. Dlaczego więc UserControls silnie odwołuje się do ResourceDictionary? Zastanawiam się, dlaczego nikt nie doświadczył tego wcześniej, jak powiedziałem w starszym pytaniu, wydaje się, że używamy słowników zasobów a XAML absolutnie się myli, w przeciwnym razie zastanawiam się, dlaczego są tak nieefektywne.

Mam nadzieję, że ktoś może rzucić trochę światła na tę sprawę.

Z góry dzięki Nico

Author: dowhilefor, 2011-07-28

2 answers

Nie jestem pewien, czy to rozwiąże twój problem. Ale miałem podobne problemy z ResourceDictionary odniesieniem do kontroli i jej do leniwego nawodnienia . Oto post na nim. I ten kod rozwiązał moje problemy:

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);
    }
}
 6
Author: anivas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-28 10:44:37

Natknąłem się na ten sam problem z potrzebowaniem współdzielonych katalogów zasobów w dużym projekcie WPF. Czytając artykuł źródłowy i komentarze, włączyłem kilka poprawek do klasy SharedDirectory, jak sugerowano w komentarzach, które wydają się usuwać silne odniesienie (przechowywane w _sourceUri), a także sprawiają, że projektant działa poprawnie. Przetestowałem twój przykład i działa, zarówno w designerze, jak i Memprofilerze z powodzeniem nie odnotowując żadnych referencji. Chciałbym wiedzieć, czy ktoś poprawił dalej, ale to jest to, co zamierzam teraz:

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;
        }
    } 
}
 9
Author: Scott O.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-20 23:32:44