c# .net .net-4.0 garbage-collection rdlc

c# - Uso de memoria muy alto en.NET 4.0



.net-4.0 garbage-collection (5)

Bueno, esta fue una interesante.

La causa raíz resulta ser un cambio en el comportamiento de la clase LocalReport de SQL Server Reporting Services (v2010) cuando se ejecuta esto en la parte superior de .NET 4.0.

Básicamente, Microsoft modificó el comportamiento del procesamiento de RDLC de modo que cada vez que se procesaba un informe, lo hacía en un dominio de aplicación independiente. En realidad, esto se hizo específicamente para solucionar una fuga de memoria causada por la incapacidad de descargar ensamblajes de los dominios de la aplicación. Cuando la clase LocalReport procesó un archivo RDLC, en realidad crea un ensamblaje sobre la marcha y lo carga en el dominio de la aplicación.

En mi caso, debido al gran volumen de informes que estaba procesando, esto resultó en la creación de un gran número de objetos System.Runtime.Remoting.ServerIdentity. Este fue mi consejo para la causa, ya que estaba confundido sobre por qué el procesamiento de un RLDC requería una comunicación remota.

Por supuesto, para llamar a un método en una clase en otro dominio de aplicación, la comunicación remota es exactamente lo que usa. En .NET 3.5, esto no era necesario ya que, de forma predeterminada, el RDLC-assembly se cargaba en el mismo dominio de la aplicación. En .NET 4.0, sin embargo, se crea un nuevo dominio de aplicación por defecto.

La solución fue bastante fácil. Primero tenía que habilitar la política de seguridad heredada con la siguiente configuración:

<runtime> <NetFx40_LegacySecurityPolicy enabled="true"/> </runtime>

A continuación, tuve que forzar que los RDLC se procesaran en el mismo dominio de aplicación que mi servicio llamando a lo siguiente:

myLocalReport.ExecuteReportInCurrentAppDomain(AppDomain.CurrentDomain.Evidence);

Esto resolvió el problema.

Tengo un servicio de Windows C # que recientemente cambié de .NET 3.5 a .NET 4.0. No se hicieron otros cambios en el código.

Cuando se ejecutaba en 3.5, la utilización de la memoria para una carga de trabajo dada era de aproximadamente 1,5 GB de memoria y el rendimiento era de 20 X por segundo. (La X no importa en el contexto de esta pregunta).

El mismo servicio exacto que se ejecuta en 4.0 usa entre 3GB y 5GB + de memoria, y obtiene menos de 4 X por segundo. De hecho, el servicio generalmente terminará bloqueándose a medida que el uso de la memoria continúe aumentando hasta que mi sistema esté ubicado al 99% de uso y el intercambio de archivos de la página se vuelva loco.

No estoy seguro si esto tiene que ver con la recolección de basura, o qué, pero estoy teniendo problemas para resolverlo. Mi servicio de ventana utiliza el GC "Servidor" a través del modificador de archivo de configuración que se muestra a continuación:

<runtime> <gcServer enabled="true"/> </runtime>

Cambiar esta opción a falso no parece marcar la diferencia. Además, de la lectura que he hecho en el nuevo GC en 4.0, los grandes cambios solo afectan al modo GC de la estación de trabajo, no al modo GC del servidor. Entonces, GC no tiene nada que ver con el problema.

Ideas?


Me encontré con este problema exacto. Y es cierto que los dominios de la aplicación se crean y no se limpian. Sin embargo, no recomendaría volver a heredar. Pueden ser limpiados por ReleaseSandboxAppDomain ().

LocalReport report = new LocalReport(); ... report.ReleaseSandboxAppDomain();

Algunas otras cosas que también hago para limpiar:

Anular la suscripción a cualquier evento de Procesamiento de subinforme, Borrar orígenes de datos, Eliminar el informe.

Nuestro servicio de Windows procesa varios informes por segundo y no hay filtraciones.


Para completar, si alguien está buscando la configuración equivalente de ASP.Net web.config , es:

<system.web> <trust legacyCasModel="true" level="Full"/> </system.web>

ExecuteReportInCurrentAppDomain funciona de la misma manera.

Gracias a esta referencia de Social MSDN .


Parece que Microsoft intentó colocar el informe en su propio espacio de memoria separado para evitar todas las pérdidas de memoria en lugar de corregirlas. Al hacerlo, introdujeron algunos bloqueos difíciles, y terminaron teniendo más pérdidas de memoria de todos modos . Parece que almacenan en caché la definición del informe, pero nunca lo utilizan y nunca lo limpian, y cada informe nuevo crea una nueva definición de informe, ocupando más y más memoria.

Jugué haciendo lo mismo: usar un dominio de aplicación separado y ordenar el informe. Creo que es una solución terrible y hace un desastre muy rápido.

Lo que hice en su lugar es similar: dividir la parte de informes de su programa en su propio programa de informes por separado. Esto resulta ser una buena forma de organizar tu código de todos modos.

La parte difícil es pasar información al programa separado. Utilice la clase Process para iniciar una nueva instancia del programa de informes y pasar los parámetros que necesita en la línea de comandos. El primer parámetro debe ser un enum o valor similar que indique el informe que se debe imprimir. Mi código para esto en el programa principal se ve algo así como:

const string sReportsProgram = "SomethingReports.exe"; public static void RunReport1(DateTime pDate, int pSomeID, int pSomeOtherID) { RunWithArgs(ReportType.Report1, pDate, pSomeID, pSomeOtherID); } public static void RunReport2(int pSomeID) { RunWithArgs(ReportType.Report2, pSomeID); } // TODO: currently no support for quoted args static void RunWithArgs(params object[] pArgs) { // .Join here is my own extension method which calls string.Join RunWithArgs(pArgs.Select(arg => arg.ToString()).Join(" ")); } static void RunWithArgs(string pArgs) { Console.WriteLine("Running Report Program: {0} {1}", sReportsProgram, pArgs); var process = new Process(); process.StartInfo.FileName = sReportsProgram; process.StartInfo.Arguments = pArgs; process.Start(); }

Y el programa de informes se ve algo así como:

[STAThread] static void Main(string[] pArgs) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var reportType = (ReportType)Enum.Parse(typeof(ReportType), pArgs[0]); using (var reportForm = GetReportForm(reportType, pArgs)) Application.Run(reportForm); } static Form GetReportForm(ReportType pReportType, string[] pArgs) { switch (pReportType) { case ReportType.Report1: return GetReport1Form(pArgs); case ReportType.Report2: return GetReport2Form(pArgs); default: throw new ArgumentOutOfRangeException("pReportType", pReportType, null); } }

GetReportForm métodos GetReportForm deben extraer la definición del informe, utilizar los argumentos relevantes para obtener el conjunto de datos, pasar los datos y cualquier otro argumento al informe, y luego colocar el informe en un visor de informes en un formulario y devolver una referencia al formulario. Tenga en cuenta que es posible extraer gran parte de este proceso para que, básicamente, pueda decir ''proporcione un formulario para este informe a partir de este ensamblado utilizando estos datos y estos argumentos''.

También tenga en cuenta que ambos programas deben poder ver sus tipos de datos que son relevantes para este proyecto, así que con suerte habrá extraído sus clases de datos en su propia biblioteca, a la cual ambos programas pueden compartir una referencia. No funcionaría tener todas las clases de datos en el programa principal, porque tendría una dependencia circular entre el programa principal y el programa de informe.

No vuelvas a hacerlo con los argumentos, tampoco. Haga cualquier consulta de base de datos que necesite en el programa de informes; no pase una gran lista de objetos (que probablemente no funcionaría de todos modos). Debería pasar cosas simples como campos de ID de base de datos, rangos de fechas, etc. Si tiene parámetros particularmente complejos, es posible que necesite insertar esa parte de la IU en el programa de informes y no pasarlos como argumentos en la línea de comando.

También puede poner una referencia al programa de informes en su programa principal, y el .exe resultante y cualquier .dll relacionado se copiarán a la misma carpeta de salida. A continuación, puede ejecutarlo sin especificar una ruta y simplemente usar el nombre de archivo ejecutable por sí mismo (es decir: "SomethingReports.exe"). También puede eliminar los dlls de informes del programa principal.

Un problema con esto es que obtendrá un error manifiesto si nunca ha publicado realmente el programa de informes. Simplemente dummy publícalo una vez, para generar un manifiesto y luego funcionará.

Una vez que tenga esto funcionando, es muy agradable ver que la memoria de su programa regular se mantiene constante al imprimir un informe. Aparece el programa de informes, que ocupa más memoria que su programa principal, y luego desaparece, y lo limpia completamente con su programa principal sin ocupar más memoria de la que ya tenía.

Otro problema podría ser que cada instancia de informe ahora ocupará más memoria que antes, ya que ahora son programas completos por separado. Si el usuario imprime muchos informes y nunca los cierra, consumirá mucha memoria muy rápido. Pero creo que esto es mucho mejor, ya que esa memoria puede recuperarse fácilmente simplemente cerrando los informes.

Esto también hace que sus informes sean independientes de su programa principal. Pueden permanecer abiertos incluso después de cerrar el programa principal, y puede generarlos manualmente desde la línea de comandos o desde otras fuentes también.


Tu podrías querer

Quizás alguna API ha cambiado la semántica o incluso puede haber un error en la versión 4.0 del marco