Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Aurélien GALTIER

The dark world of .Net

Dispose, Finalize et Optimisation de code égal bug ?

Dernièrement l'équipe de recette ou je travaille, test la nouvelle application et nous a remonté des bugs sur l'impression des documents XPS. Hors lorsque je teste sur mon poste cela marche correctement.

Le bug : Impression de 3 ou 4 pages puis une erreur sur le faite que le flux est fermé. Quand je fais F5 et que je teste j'imprime facilement les 20 pages qui compose mon XPS.

Alors d’où viens le problème ?

Après avoir tester en debug en release, avec la version de la recette… Le problème ne se reproduit que lorsque l'application est compilé en release et quel est lancé sans faire F5 (Sans debugger attaché)

Alors si le problème se reproduit seulement en release avec exactement les mêmes données. C'est que cela vient d'une option différente entre DEBUG et RELEASE.

Et l'option qui fait tous planter c'est : "Activer les optimisations" Pour en être sur je désactive l'option sur l'assembly ou se fais l'impression. Je compile exécute l'application et tous fonctionne.

Attention : La désactivation de cette options n'est pas recommandé dans un environnement de production.

Pourquoi mon code optimisé plante ?

Diverse raison peuvent entrainer un problème lorsque le code est optimisé. Ces bugs sont assez rare. Dans mon cas cela vient d'un objet qui implémente le paterne Disposable et Finalize.

J'ai fais une petite application qui reproduit le bug.

Je créé une classe DisposableObject :

    public sealed class DisposableObject : IDisposable
    {
        public MemoryStream MyStream { get; private set; }

        public DisposableObject(byte[] file)
        {
            this.MyStream = new MemoryStream(file);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool disposing)
        {
            if (!disposing)
            {
                this.MyStream.Dispose();
            }
        }

        ~DisposableObject()
        {
            this.Dispose(false);
        }
    }

Cette classe créé un MemoryStream a partir du tableau de byte passé au constructeur. Elle implémente Idisposable et donc dispose mon MemoryStream quand elle est disposé ou détruite. D'une manière générale évité d'exposer des objets Disposable (MemoryStream) en propriété public.

Et le corps de mon application :

            try
            {
                byte[] b = new byte[int.MaxValue / 100];

                var disposableObject = new DisposableObject(b);
                var stream = disposableObject.MyStream;

                ReadStream(stream);

                Console.WriteLine("End");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.ReadLine();

Je créé un tableau de byte assez gros (Pour que l'application mette un peut de temps a s'exécuté)

J'utilise mon objet pour récupéré mon MemoryStream et le lire dans la méthode ReadStream.

La seul erreur dans ce code c'est que je n'ai pas utilisé de using pour mon DisposableObject. Dans un bout de code aussi simple on ce dis que le using si il n'est pas exécuté c'est pas trop grave. De toutes façons le garbage est la pour nous détruire notre objet comme un grand.

Je sélectionne le mode Release, appuis sur F5. L'application lis le MemoryStream, met un peut de temps a se terminer. Mais tous fonctionne correctement.

Je vais dans le répertoire de Release et double clique sur l'exécutable (Je n'ai pas modifié le code, ni recompilé). Et la on obtient une erreur :

System.ObjectDisposedException: Impossible d'accéder à un Stream fermé.

Ce qui se passe :

· L'application créé un MemoryStream

· Elle commence a lire le MemoryStream dans la méthode ReadStream

· Le garbage collector décide de passer pour libéré la mémoire.

· Il détecte que disposableObject n'est plus jamais utilisé et décide de le détruire.

· Notre destructeur appelle dispose qui lui dispose le MemoryStream.

Pourquoi en faisant F5 je ne reproduit pas le problème ? Simplement car vous êtes dans un environnement de développement. Pour que le debugger puisse agir correctement, il y a beaucoup d'optimisation qui sont automatiquement désactivé.

La solution

La solution est toutes simple. Et je l'ai déjà donné au dessus. Lorsque vous utiliser un objet qui implémenter le paterne Disposable, utilisé le mot clef using.

Evité aussi d'utilisé le destructeur. En générale votre classe vas être garbagé. Et si vous avez utilisé le mot clef using, vous aurez alors libéré les ressources au moment ou cela vous convient. Dans mon cas c’est-à-dire après le ReadStream.

Source

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Posted: mercredi 3 octobre 2012 17:33 par agaltier
Classé sous : , ,

Commentaires

KooKiz a dit :

Un moyen moins aléatoire de reproduire le problème :

       static void Main(string[] args)

       {

           try

           {

               byte[] b = new byte[0];

               var disposableObject = new DisposableObject(b);

               var stream = disposableObject.MyStream;

               GC.Collect();

               GC.WaitForPendingFinalizers();

               Console.WriteLine(stream.Length);

               Console.WriteLine("End");

           }

           catch (Exception ex)

           {

               Console.WriteLine(ex);

           }

           Console.ReadLine();

       }

# octobre 3, 2012 17:59

JeremyJeanson a dit :

Bonjour Aurélien,

Ce genre de faute est courante, mais faire porter le chapeau à "Dispose, Finalize et Optimisation" me dérange un peu.

Le using ne faisant que préserver l'instance utilisée par la référence, je suis d'accord sur la conclusion. Mais dans un cas plus évolué, la mise en cascade des using risque de devenir un vrai cauchemar.

On sait très bien que dans le cas d'un rapport (ou tout autre élément qui prend des données d'une source .net), il faut que l'on passe des clones des instances et non leurs références.

L'exemple de KooKiz résume bien la chose :

- Je créer

- Je passe une référence à mon rapport

- Le GC supprime

- Je consulte une référence sur ... "rien"

C'est un peu comme passer une expression LINQ à un rapport au lieu d'une énumération résultante de l'executionde l'expression LINQ.

# octobre 5, 2012 15:36

agaltier a dit :

@KooKiz : Oui effectivement c’est un moyen moins aléatoire. Par contre je souhaitais pour moi reproduire le bug dans les conditions les plus proches de mon client ;)

@JeremyJeanson : Je suis d’accord c’est un peu comme si on utilisait une requête LINQ. Au contraire que ce problème se produit seulement lorsque les optimisations sont activées. Car la CLR détecte que mon objet n’est plus utilisé et il se retrouve détruit. Mon but était de mettre l’accent sur 2 choses :

-Des bugs qui peuvent apparaitre seulement en release.

-Et aussi de toujours disposé ces objets explicitement lorsque ceux si implémente IDispose.

Dans le problème de mon client, je n’ai pas passé de donnée à un rapport. C’est le flux qui correspond à mon fichier XPS qui est fermé. Je n’ai aucun intérêt de cloner ce flux car il peut être grand et il rajouterait du temps d’exécution pour l’impression de ce fichier.

Les using en cascade je ne suis pas non plus très fan. Mais si tous les objets sont disposable alors c’est que d’une manière ou d’une autre ils en ont besoin. La faute d’oublier le using est très courante mais engendre rarement un bug seulement en release.

# octobre 12, 2012 16:31
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01