Pérdida de memoria usando StreamReader y XmlSerializer
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.
La filtración está aquí:
new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))
XmlSerializer
utiliza 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é.
En primer lugar, debe deshacerse de su StreamReader incluso si se produce una excepción (lo mismo para XMLObj). Utilice la using
declaració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.
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);
}
}
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; }
}
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.