Pérdida de memoria usando StreamReader y XmlSerializer

Resuelto Alex999 asked hace 10 años • 6 respuestas

He estado buscando en Google durante las últimas horas y probando diferentes cosas, pero parece que no puedo llegar al fondo de esto...

Cuando ejecuto este código, el uso de la memoria crece continuamente.

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 es un objeto personalizado

[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);
    }
}

Intenté agregar GC.Collect(); pero eso no parece hacer nada.

Alex999 avatar May 28 '14 02:05 Alex999
Aceptado

La filtración está aquí:

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

XmlSerializerutiliza la generación de ensamblajes y los ensamblajes no se pueden recopilar. Realiza cierta caché/reutilización automática para los escenarios de constructor más simplesnew XmlSerializer(Type) ( , etc.), pero no para este escenario. En consecuencia, debes almacenarlo en caché manualmente:

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

y utilice la instancia del serializador en caché.

Marc Gravell avatar May 27 '2014 19:05 Marc Gravell

En primer lugar, debe deshacerse de su StreamReader incluso si se produce una excepción (lo mismo para XMLObj). Utilice la usingdeclaración. Actualmente no eliminará cuando se produzca una excepción.

Es muy poco probable que tengas una pérdida de memoria. Lo más probable es que el tiempo de ejecución simplemente no haya elegido recopilar memoria todavía. Incluso GC.Collect no necesariamente hará que se libere memoria.

Me he encontrado con situaciones similares al procesar archivos XML muy grandes (varios GB). Aunque el tiempo de ejecución ocupa la mayor parte de la memoria disponible, la libera cuando la presión de la memoria lo justifica.

Puede utilizar el generador de perfiles de memoria en Visual Studio para ver qué memoria está asignada y en qué generación reside.

ACTUALIZAR

Vale la pena investigar el comentario de @KaiEichinger. Indica que XmlSerializer puede estar creando una nueva definición de objeto en caché para cada iteración del bucle.

El constructor XMLSerializer crea el ensamblado temporal para el tipo que se va a serializar mediante reflexión y, dado que la generación de código es costosa, el ensamblado se almacena en caché en la memoria por tipo. Pero muchas veces el nombre de la raíz se cambiará y puede ser dinámico y no almacenará en caché el ensamblaje dinámico. Entonces, cada vez que se llama a la línea de código anterior, se carga el nuevo ensamblado cada vez y permanecerá en la memoria hasta que se descargue AppDomain.

Eric J. avatar May 27 '2014 19:05 Eric J.

Desde MSDN: ingrese la descripción del enlace aquí

Para aumentar el rendimiento, la infraestructura de serialización XML genera dinámicamente ensamblados para serializar y deserializar tipos específicos. La infraestructura encuentra y reutiliza esos ensamblajes. Este comportamiento se produce sólo cuando se utilizan los siguientes constructores:

XmlSerializer.XmlSerializer(Tipo)

XmlSerializer.XmlSerializer (Tipo, Cadena)

Si utiliza cualquiera de los otros constructores, se generan varias versiones del mismo ensamblado y nunca se descargan, lo que provoca una pérdida de memoria y un rendimiento deficiente. La solución más sencilla es utilizar uno de los dos constructores mencionados anteriormente. De lo contrario, debe almacenar en caché los ensamblados en una Hashtable, como se muestra en el siguiente ejemplo.

=> Entonces, para solucionarlo debes usar este constructor XmlSerializer xml = new XmlSerializer(typeof(XMLObj)) en lugar deXmlSerializer xml = new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"));

y agregue el atributo XML raíz a la clase 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);
    }
}
Alex Nguyen avatar Mar 21 '2016 04:03 Alex Nguyen

Estoy usando una clase de "caché" para evitar la creación de instancias de xmlserializer cada vez que necesitas serializar algo (también agregué un XmlCommentAttribute para agregar comentarios a las propiedades serializadas en la salida xml), para mí funciona como sharm, espero ayudar a alguien con esto :

 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; }
}
Danilow avatar Feb 14 '2017 15:02 Danilow

Recientemente me enfrenté al mismo problema con el último .NET Core 3.1 y el almacenamiento en caché XMLSerializer (propuesto aquí) funcionó. Lo peor de esta pérdida de memoria es que no se puede localizar claramente desde el volcado de memoria, probé dotMemory de Jetbrains y todo parecía estar bien según el resultado del volcado analizado, pero la cantidad de memoria utilizada por la aplicación (tamaño del volcado) y la cantidad de memoria utilizada por la aplicación mostrada en el informe dotMemory fueron significativamente diferentes. dotMemory mostró solo unos pocos MB de memoria utilizados por la aplicación. Originalmente pensé que el problema era causado por WCF, lo cual es realmente complicado hacerlo funcionar en .NET Core cuando el contrato (WSDL) usa una codificación diferente a la de utf-8 y especialmente cuando el contrato contiene puntos en los nombres de los métodos (The El lado del servidor fue escrito en PHP) Eso no sería un problema con .Net Framework, pero las herramientas para .Net Core son diferentes. Tuve que ajustar WSDL manualmente y agregar algunas clases que faltaban en la implementación de .Net Core para poder trabajar con diferentes codificaciones.

Martin Rulf avatar Aug 07 '2020 06:08 Martin Rulf