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 :