29

Tengo un código C# que usa CSharpCodeProvider.CompileAssemblyFromSource para crear un ensamblaje en la memoria. Después de que el ensamblado se recolectó como basura, mi aplicación usa más memoria de la que tenía antes de crear el ensamblaje. Mi código está en una aplicación web ASP.NET, pero he duplicado este problema en WinForm. Estoy usando System.GC.GetTotalMemory (true) y Red Gate ANTS Memory Profiler para medir el crecimiento (alrededor de 600 bytes con el código de muestra).¿Cómo puedo evitar que CompileAssemblyFromSource pierda memoria?

A partir de la búsqueda que he hecho, parece que la fuga proviene de la creación de nuevos tipos, no de ningún objeto al que me refiero. Algunas de las páginas web que he encontrado han mencionado algo sobre AppDomain, pero no entiendo. ¿Puede alguien explicar qué está pasando aquí y cómo solucionarlo?

He aquí algunos ejemplos de código de fugas:

private void leak() 
{ 
    CSharpCodeProvider codeProvider = new CSharpCodeProvider(); 
    CompilerParameters parameters = new CompilerParameters(); 
    parameters.GenerateInMemory = true; 
    parameters.GenerateExecutable = false; 

    parameters.ReferencedAssemblies.Add("system.dll"); 

    string sourceCode = "using System;\r\n"; 
    sourceCode += "public class HelloWord {\r\n"; 
    sourceCode += " public HelloWord() {\r\n"; 
    sourceCode += " Console.WriteLine(\"hello world\");\r\n"; 
    sourceCode += " }\r\n"; 
    sourceCode += "}\r\n"; 

    CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, sourceCode); 
    Assembly assembly = null; 
    if (!results.Errors.HasErrors) 
    { 
     assembly = results.CompiledAssembly; 
    } 
} 

Actualización 1: Esta pregunta puede estar relacionada: Dynamically loading and unloading a a dll generated using CSharpCodeProvider

Actualización 2: Tratar de entender dominios de aplicación más, me encontré con esto: What is an application domain - an explanation for .Net beginners

Actualización 3: Para aclarar, estoy buscando ng para una solución que proporciona la misma funcionalidad que el código anterior (compilar y proporcionar acceso al código generado) sin pérdida de memoria. Parece que la solución implicará la creación de un nuevo dominio de aplicación y clasificación.

+0

pregunta muy fresco. Tendré un ejemplo de cómo hacer esto usando otro dominio de aplicación para el final de hoy (actualmente estoy almorzando, luego volviendo al trabajo ...). – Charles

+0

¿Qué piensas hacer con el conjunto resultante? ¿Es solo para una ejecución de una vez o vas a esperar? – madaboutcode

+1

@LightX Voy a conservarlo por un tiempo e invocar a los miembros según sea necesario, pero cuando haya una nueva versión del código fuente disponible, querré volcarlo y crear un nuevo ensamblado basado en el nuevo código Sin la corrección AppDomain, este ciclo de creación repetida de ensamblajes (aunque dejo de hacer referencia a las versiones anteriores) hace que el uso de la memoria crezca. – Nogwater

Respuesta

13

No se admite la descarga de un conjunto. Alguna información sobre por qué se puede encontrar here. Se puede encontrar algo de información sobre el uso de un dominio de aplicación here.

+4

Jeremy está en lo cierto, no hay forma de forzar a .NET a descargar un ensamblado. Vas a tener que usar un dominio de aplicación (que es un poco molesto, pero realmente no tan malo), por lo que puedes volcar todo cuando hayas terminado. –

+0

Entonces, lo que estoy escuchando es que no puedes descargar un ensamblaje, a menos que lo cargues en su propio Dominio de Aplicación y luego lo vuelques todo. Eso suena como una solución viable, entonces, ¿cómo hago eso para compilar y usar código dinámico? – Nogwater

7

También puede encontrar esta entrada de blog útil: Using AppDomain to Load and Unload Dynamic Assemblies. Proporciona un ejemplo de código que demuestra cómo crear un AppDomain, cargar un ensamblado (dinámico) en él, trabajar en el nuevo AppDomain y luego descargarlo.

Editar: enlace fijo como se indica en los comentarios a continuación.

+0

Esta parece ser la dirección que necesitaré saber. ¿Puedo usar esto con CompileAssemblyFromSource? ¿Puedo crear nuevos AppDomains desde una aplicación web? – Nogwater

+3

Puede intentar llamar a CompileAssemblyFromSource en un dominio de aplicación separado y luego descargar ese dominio cuando haya terminado. –

+1

El enlace anterior debe ser: http://blogs.claritycon.com/steveholstad/2007/06/28/using-appdomain-to-load-and-unload-dynamic-assemblies/ –

30

Creo que tengo una solución de trabajo. Gracias a todos por apuntarme en la dirección correcta (espero).

Los ensamblajes no se pueden descargar directamente, pero AppDomains sí. Creé una biblioteca de ayuda que se carga en un nuevo AppDomain y puede compilar un nuevo ensamblado a partir del código. Así es como se ve la clase en esa biblioteca auxiliar:

public class CompilerRunner : MarshalByRefObject 
{ 
    private Assembly assembly = null; 

    public void PrintDomain() 
    { 
     Console.WriteLine("Object is executing in AppDomain \"{0}\"", 
      AppDomain.CurrentDomain.FriendlyName); 
    } 

    public bool Compile(string code) 
    { 
     CSharpCodeProvider codeProvider = new CSharpCodeProvider(); 
     CompilerParameters parameters = new CompilerParameters(); 
     parameters.GenerateInMemory = true; 
     parameters.GenerateExecutable = false; 
     parameters.ReferencedAssemblies.Add("system.dll"); 

     CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code); 
     if (!results.Errors.HasErrors) 
     { 
      this.assembly = results.CompiledAssembly; 
     } 
     else 
     { 
      this.assembly = null; 
     } 

     return this.assembly != null; 
    } 

    public object Run(string typeName, string methodName, object[] args) 
    { 
     Type type = this.assembly.GetType(typeName); 
     return type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, assembly, args); 
    } 

} 

Es muy básico, pero fue suficiente para probar. PrintDomain está ahí para verificar que viva en mi nuevo AppDomain. La compilación toma un código fuente e intenta crear un ensamblaje. Ejecutar nos permite probar la ejecución de métodos estáticos a partir del código fuente dado.

Así es como yo uso la biblioteca de ayuda:

static void CreateCompileAndRun() 
{ 
    AppDomain domain = AppDomain.CreateDomain("MyDomain"); 

    CompilerRunner cr = (CompilerRunner)domain.CreateInstanceFromAndUnwrap("CompilerRunner.dll", "AppDomainCompiler.CompilerRunner");    
    cr.Compile("public class Hello { public static string Say() { return \"hello\"; } }");    
    string result = (string)cr.Run("Hello", "Say", new object[0]); 

    AppDomain.Unload(domain); 
} 

Básicamente crea el dominio, se crea una instancia de mi clase de ayuda (CompilerRunner), lo utiliza para compilar una nueva asamblea (oculto), se ejecuta un código de ese nuevo ensamblado, y luego descarga el dominio para liberar memoria.

Notará el uso de MarshalByRefObject y CreateInstanceFromAndUnwrap.Estos son importantes para garantizar que la biblioteca auxiliar realmente viva en el nuevo dominio.

Si alguien nota algún problema o tiene sugerencias para mejorar esto, me encantaría escucharlos.

+2

Algo que se debe tener en cuenta al cruzar los límites del dominio. Si no obtiene el tipo marshalled de MarshalByRefObject, la clasificación utilizará la semántica de copia por valor. Esto puede dar como resultado una ejecución muy lenta, ya que la comunicación a través del límite del dominio será muy hablante. Si no puede hacer que sus tipos se deriven de MarshalByRefObject, es posible que desee crear un objeto proxy genérico que instancia en el dominio de aplicación secundario que deriva de MarshalByRefObject, que media la comunicación. Si implementa MarshalByRefObject, tenga cuidado con la duración del objeto e implemente ... – jrista

+1

una anulación de * InitializeLifetimeService * de manera que mantenga el objeto activo el tiempo suficiente. Si desea que el objeto persista para siempre, devuelva nulo de InitializeLifetimeService. – jrista

+0

@jrista Gracias por el puntero. ¿El tipo marshalled en el ejemplo anterior es la clase Hello (la que se accede con this.assembly.GetType (typeName))? – Nogwater

2

¿Se puede esperar hasta .NET 4.0? Con él puede usar árboles de expresión y el DLR para generar código dinámicamente sin el problema de pérdida de memoria de código genérico.

Otra opción es utilizar .NET 3.5 con un lenguaje dinámico como IronPython.

EDIT: Expresión árbol Ejemplo

http://www.infoq.com/articles/expression-compiler

+0

Me gustan sus sugerencias, pero desafortunadamente no me funcionarán en este proyecto (todavía no estamos usando .NET 4 y no podemos usar IronPython (solo C#)). Si no te importa, podrías completar tu respuesta relacionada con los árboles de expresión. Podría ayudar a alguien más. ¿Se pueden usar para tomar algo almacenado en una cadena y convertirlo en código de trabajo que se puede descargar de la memoria? Gracias. – Nogwater

+0

Agregué un enlace a un artículo sobre árboles de expresiones. –

+3

Si crea dinámicamente código con DLR, ocupará espacio cuando lo ejecute. ¿Podrías liberar esto en .Net 4 sin appdomains? La pérdida de memoria en la pregunta no se debe al código-gen, sino a la carga de un ensamblado en el mismo dominio de aplicación (por lo que no es realmente una fuga) que no se puede liberar. –

Cuestiones relacionadas