web-dev-qa-db-fra.com

Fuite de mémoire à l'aide de StreamReader et XmlSerializer

Je suis sur Google depuis quelques heures et j'ai essayé différentes choses, mais je ne peux pas sembler être au fond de cette ...

Lorsque je lance ce code, l'utilisation de la mémoire augmente continuellement. 

while (true)
{
    try
    {
        foreach (string sym in stringlist)
        {
            StreamReader r = new StreamReader(@"C:\Program Files\" + sym + ".xml");
            XmlSerializer xml = new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"));
            XMLObj obj = (XMLObj)xml.Deserialize(r);                       
            obj.Dispose();
            r.Dispose();
            r.Close();
        }
    }    
    catch(Exception ex) 
    {
        Console.WriteLine(ex.ToString()); 
    }
    Thread.Sleep(1000);
    Console.Clear();
}

XMLObj est un objet personnalisé

[Serializable()]
public class XMLObj: IDisposable
{
    [XmlElement("block")]
    public List<XMLnode> nodes{ get; set; }

    public XMLObj() { }

    public void Dispose()
    {
        nodes.ForEach(n => n.Dispose());
        nodes= null;

        GC.SuppressFinalize(this);
    }
}

J'ai essayé d'ajouter dans GC.Collect (); mais cela ne semble rien faire.

18
Alex999

La fuite est ici:

new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))

XmlSerializer utilise la génération d'assemblage et les assemblys ne peuvent pas être collectés. Il fait un peu de cache/réutilisation automatique pour les scénarios simplest / constructeur (new XmlSerializer(Type), etc), mais pas pour ce scénario. Par conséquent, vous devriez le mettre en cache manuellement:

static readonly XmlSerializer mySerializer =
    new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))

et utilisez l'instance de sérialiseur mise en cache.

47
Marc Gravell

Tout d’abord, vous devriez vous débarrasser de votre StreamReader même si une exception est levée (idem pour XMLObj). Utilisez l'instruction using. Actuellement, vous ne disposerez pas lorsqu'une exception est levée.

Il est très peu probable que vous ayez une fuite de mémoire. Plus probablement, le moteur d'exécution n'a simplement pas choisi de collecter de la mémoire pour le moment. Même GC.Collect ne provoquera pas nécessairement la libération de mémoire.

J'ai rencontré des situations similaires lors du traitement de très gros fichiers XML (multi-Go). Même si le moteur d'exécution saisit la plus grande partie de la mémoire disponible, il la libère quand la pression de la mémoire le justifie.

Vous pouvez utiliser le profileur de mémoire dans Visual Studio pour voir quelle mémoire est allouée et dans quelle génération elle réside.

METTRE &AGRAVE; JOUR

Le commentaire de @KaiEichinger mérite d’être étudié. Il indique que XmlSerializer est en train de créer une nouvelle définition d'objet mis en cache pour chaque itération de boucle.

Le constructeur XMLSerializer crée l'assembly temporaire pour le type à sérialiser à l'aide de la réflexion. Puisque la génération de code est coûteuse, l'assembly est mis en cache dans la mémoire par type. Mais souvent, le nom de la racine sera modifié et peut être dynamique sans que l’assembly dynamique ne soit mis en cache. Ainsi, chaque fois que la ligne de code ci-dessus est appelée, elle charge le nouvel assemblage à chaque fois et reste en mémoire jusqu'à ce que AppDomain soit déchargé.

8
Eric J.

Depuis MSDN: entrez la description du lien ici

Pour augmenter les performances, l'infrastructure de sérialisation XML génère de manière dynamique des assemblys pour sérialiser et désérialiser les types spécifiés. L'infrastructure trouve et réutilise ces assemblages. Ce problème se produit uniquement lors de l'utilisation des constructeurs suivants:

XmlSerializer.XmlSerializer (Type)

XmlSerializer.XmlSerializer (Type, Chaîne)

Si vous utilisez l'un des autres constructeurs, plusieurs versions du même assemblage sont générées et jamais déchargées, ce qui entraîne une fuite de mémoire et des performances médiocres. La solution la plus simple consiste à utiliser l'un des deux constructeurs mentionnés précédemment. Sinon, vous devez mettre en cache les assemblys dans une table de hachage, comme illustré dans l'exemple suivant.

=> Donc, pour résoudre ce problème, vous devez utiliser ce constructeur XmlSerializer xml = new XmlSerializer(typeof(XMLObj)) au lieu de XmlSerializer xml = new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"));

et ajouter un attribut XML racine dans la classe XMLObj.

[Serializable()]
[XmlRoot("root")]
public class XMLObj: IDisposable
{
    [XmlElement("block")]
    public List<XMLnode> nodes{ get; set; }

    public XMLObj() { }

    public void Dispose()
    {
        nodes.ForEach(n => n.Dispose());
        nodes= null;

        GC.SuppressFinalize(this);
    }
}
4
Alex Nguyen

J'utilise une classe "cache" pour éviter d'instancier xmlserializer à chaque fois que vous devez sérialiser quelque chose (j'ai également ajouté un XmlCommentAttribute pour ajouter des commentaires aux propriétés sérialisées dans la sortie xml). Pour moi, cela fonctionne comme sharm. J'espère aider quelqu'un :

 public static class XmlSerializerCache
{
    private static object Locker = new object();
    private static Dictionary<string, XmlSerializer> SerializerCacheForUtils = new Dictionary<string, XmlSerializer>();

    public static XmlSerializer GetSerializer<T>()
    {
        return GetSerializer<T>(null);
    }
    public static XmlSerializer GetSerializer<T>(Type[] ExtraTypes)
    {
        return GetSerializer(typeof(T), ExtraTypes);
    }
    public static XmlSerializer GetSerializer(Type MainTypeForSerialization)
    {
        return GetSerializer(MainTypeForSerialization, null);
    }
    public static XmlSerializer GetSerializer(Type MainTypeForSerialization, Type[] ExtraTypes)
    {
        string Signature = MainTypeForSerialization.FullName;
        if (ExtraTypes != null)
        {
            foreach (Type Tp in ExtraTypes)
                Signature += "-" + Tp.FullName;
        }

        XmlSerializer XmlEventSerializer;
        if (SerializerCacheForUtils.ContainsKey(Signature))
            XmlEventSerializer = SerializerCacheForUtils[Signature];
        else
        {
            if (ExtraTypes == null)
                XmlEventSerializer = new XmlSerializer(MainTypeForSerialization);
            else
                XmlEventSerializer = new XmlSerializer(MainTypeForSerialization, ExtraTypes);

            SerializerCacheForUtils.Add(Signature, XmlEventSerializer);
        }
        return XmlEventSerializer;
    }

    public static T Deserialize<T>(XDocument XmlData)
    {
        return Deserialize<T>(XmlData, null);
    }
    public static T Deserialize<T>(XDocument XmlData, Type[] ExtraTypes)
    {
        lock (Locker)
        {
            T Result = default(T);
            try
            {
                XmlReader XmlReader = XmlData.Root.CreateReader();
                XmlSerializer Ser = GetSerializer<T>(ExtraTypes);
                Result = (T)Ser.Deserialize(XmlReader);
                XmlReader.Dispose();
                return Result;
            }
            catch (Exception Ex)
            {
                throw new Exception("Could not deserialize to " + typeof(T).Name, Ex);
            }
        }
    }
    public static T Deserialize<T>(string XmlData)
    {
        return Deserialize<T>(XmlData, null);
    }
    public static T Deserialize<T>(string XmlData, Type[] ExtraTypes)
    {
        lock (Locker)
        {
            T Result = default(T);
            try
            {

                using (MemoryStream Stream = new MemoryStream())
                {
                    using (StreamWriter Writer = new StreamWriter(Stream))
                    {
                        Writer.Write(XmlData);
                        Writer.Flush();
                        Stream.Position = 0;
                        XmlSerializer Ser = GetSerializer<T>(ExtraTypes);
                        Result = (T)Ser.Deserialize(Stream);
                        Writer.Close();
                    }
                }
                return Result;
            }
            catch (Exception Ex)
            {
                throw new Exception("Could not deserialize to " + typeof(T).Name, Ex);
            }
        }
    }

    public static XDocument Serialize<T>(T Object)
    {
        return Serialize<T>(Object, null);
    }
    public static XDocument Serialize<T>(T Object, Type[] ExtraTypes)
    {
        lock (Locker)
        {
            XDocument Xml = null;
            try
            {
                using (MemoryStream stream = new MemoryStream())
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    ns.Add("", "");

                    using (StreamReader Reader = new StreamReader(stream))
                    {
                        XmlSerializer Serializer = GetSerializer<T>(ExtraTypes);
                        var settings = new XmlWriterSettings { Indent = true };
                        using (var w = XmlWriter.Create(stream, settings))
                        {
                            Serializer.Serialize(w, Object, ns);
                            w.Flush();
                            stream.Position = 0;
                        }
                        Xml = XDocument.Load(Reader, LoadOptions.None);

                        foreach (XElement Ele in Xml.Root.Descendants())
                        {
                            PropertyInfo PI = typeof(T).GetProperty(Ele.Name.LocalName);
                            if (PI != null && PI.IsDefined(typeof(XmlCommentAttribute), false))
                                Xml.AddFirst(new XComment(PI.Name + ": " + PI.GetCustomAttributes(typeof(XmlCommentAttribute), false).Cast<XmlCommentAttribute>().Single().Value));
                        }

                        Reader.Close();
                    }
                }
                return Xml;
            }
            catch (Exception Ex)
            {
                throw new Exception("Could not serialize from " + typeof(T).Name + " to xml string", Ex);
            }
        }
    }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
    public string Value { get; set; }
}
1
Danilow

Je pense que déplacer le constructeur XMLSerializer en dehors de la boucle et mettre en cache son résultat le corrigera, explication ici

0
Peter Wishart