Fuites de mémoire avec WPF 4.0 et de grosses listes d’images

15. mars 2011

Dans le cadre d’UrzaGatherer (http://urzagatherer.codeplex.com), j’ai découvert un problème bien embêtant avec une liste d’images en haute définition.

En effet j’ai une ListBox qui me présente les cartes d’une collection dans un WrapPanel (qui porte une jolie animation):

image

Or, l’utilisateur en se promenant de collections en collections va faire changer de nombreuses fois la source de la liste et va donc déclencher de nombreuses fois le chargement de ces grosses images.

Et j’ai constaté que lorsque je réaffecte le DataContext de ma liste à une nouvelle source, les anciennes données semblent ne pas être nettoyées de la mémoire.

Ainsi, UrzaGatherer peut allègrement occuper 2Go de RAM après avoir visité une dizaine de collections (chaque collection peut peser jusqu’à 300 Mo).

Je me suis donc mis en quête de la fuite.

Mais avant d’en venir à cette dernière regardons déjà comment le binding est fait et comment la liste est construite:

 

1 - Notre ListBox


La liste est donc une ListBox avec comme ItemPanel un WrapPanel dans lequel j’ai glissé un behavior en provenance de Blend pour faire un effet sympa d’animations:

            <ListBox ItemsSource="{Binding}" x:Name="imagesList">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Image Source="{Binding Bitmap, Mode=OneWay}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Horizontal" ItemHeight="300">
                            <Interactivity:Interaction.Behaviors>
                                <Layout:FluidMoveBehavior Duration="00:00:00.5">
                                </Layout:FluidMoveBehavior>
                            </Interactivity:Interaction.Behaviors>
                        </WrapPanel>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>

 

On peut voir que l’ItemTemplate se branche sur la propriété Bitmap de notre source.

2 - La source de données


Cette dernière est effectivement composée d’une liste de cartes qui portent une propriété Bitmap que je construis ainsi:

public BitmapImage Bitmap
{
    get
    {
        if (CompletePath == null)
            return null;

        BitmapImage bitmapImage = new BitmapImage();
        bitmapImage.BeginInit();
        bitmapImage.CreateOptions = BitmapCreateOptions.None;
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
        bitmapImage.UriSource = new Uri(CompletePath);
        bitmapImage.EndInit();
        bitmapImage.Freeze();

        return bitmapImage;
    }
}

A partir du chemin CompletePath, je produis une BitmapImage. L’objectif étant de contrôler le cache et de pouvoir créer les images dans un thread séparé pour les affecter par la suite. Toutefois, dans notre exemple, je simplifie cette étape en branchant directement le contrôle Image sur mon Bitmap sans passer par la création asynchrone.

 

3 – La fuite (les fuites?)


A partir de ce code relativement simple, il me suffit de brancher 3 ou 4 collections de cartes pour voir que la mémoire n’est jamais libérée.

Mes investigations m’ont permis de voir deux soucis:

  • Le behavior de Blend garde des références sur les images et de ce fait bloque le Garbage Collector et donc laisse les images en mémoire
  • L’utilisation d’une BitmapImage semble également laisser des traces en mémoire et malgré mes efforts je n’ai pas pu libérer ces ressources. La seule solution : faire un binding vers le chemin (CompletePath) plutôt que vers ma Bitmap.

 

Je me suis donc résigner à ne plus utiliser ni le behavior ni le chargement asynchrone. Toutefois pour ce dernier point, j’ai pu faire un contournement ainsi:

  • Créer une variable BindPath sur mes cartes
  • L’initialiser à null et mettre le binding du contrôle Image dessus
  • Binder la liste (donc pour le moment aucune image ne s’affiche puisque BindPath = null)
  • Faire une boucle dans une Task qui toutes les 20 millisecondes prend une carte et met sa propriété BindPath à CompletePath. De ce fait via le fait que mes cartes sont INotityPropertyChanged, la ListBox se met à jour progressivement et donne un effet progressif agréable (plutot qu’un bon vieux figeage de l’interface à l’ancienne)

 

Si vous aussi vous trouvez des fuites dans l’utilisation de WPF 4.0, n’hésitez pas à me le signaler Sourire

WPF, .Net, UrzaGatherer

Enfin dans les bacs

1. décembre 2010

Vous l’attendiez tous, il est enfin là!

UrzaGatherer 2.2 est disponible avec un déploiement ClickOnce.

Ca se passe juste ici:http://www.catuhe.com/urzagatherer/publish.htm

Vous n’avez désormais plus aucune excuse pour ne pas vous en servir!

image

UrzaGatherer

UrzaGatherer 2.1

22. novembre 2010

UrzaGatherer 2.1 vient juste de sortir. Cette version ajoute le support de l’évaluation du prix des cartes.

Donc pour la petite histoire, l’intégralité est estimée à plus de 50.000€. Ca calme.

image

Les cartes les plus chères sont bien sur les plus rares:

image

Le Black Lotus (Alpha) reste le grand champion avec plus de 3000€ l’unité (vous noterez que l’icone en début de ligne montre que je ne l’ai pas, donc si vous cherchiez une idée de cadeau pour Noel…)


Le tout disponible sur Codeplex: http://urzagatherer.codeplex.com/

UrzaGatherer

Ouf

14. septembre 2010

La dernière version de la base de UrzaGatherer commence à peser lourd:

  • 15856 cartes
  • 19 blocks
  • 69 extensions
  • 2,7 Go d’images

 

image

UrzaGatherer

Vidéo d’utilisation d’UrzaGatherer

6. juin 2010

Un petit exemple d’utilisation rapide d’UrzaGatherer:

[youtube:UDeAOi8MEFw]

UrzaGatherer

Mise à disposition de la base complète UrzaGatherer

6. juin 2010

La base complète avec toutes les images pour UrzaGatherer est désormais disponible.

La petite bête fait actuellement 2.8Go. Ca fait un gros beef steak à télécharger. Le tout est disponible ici:

http://www.catuhe.com/urzagatherer/urzafull.bak.

Pour installer la base, il est nécessaire d’avoir SQL Server 2008 R2 Express. Par la suite, UrzaGatherer peut installer la base tout seul via une interface qui apparaitra au premier lancement.

Bon téléchargement :)

UrzaGatherer

UrzaGatherer sur CodePlex

3. juin 2010

Ca y est ! C’est le début de la gloire.

UrzaGatherer est disponible sur CodePlex à cette adresse:

http://urzagatherer.codeplex.com/

 

N’hésitez pas à me faire des retours!

UrzaGatherer

UrzaGatherer

31. mai 2010

Parmi mes nombreux vices, j’ai notamment le plaisir d’être collectionneur de cartes Magic.

Or cette collection est extrêmement riche et complexe à gérer. J’ai donc développé en WPF4.0, une application de gestion associée.

Elle utilise SQL Server 2008 R2 express pour stocker les dizaines de milliers d’images. Le système s’appuie sur Entity Framework 4.0 (qui d’ailleurs est une tuerie).

Je mettrai dans quelques temps le code source de l’application sur Codeplex.

image image

image image

WPF, .Net, UrzaGatherer