2009-11-06 15 views
13

Mi escenario es el siguiente:Uso dominio de aplicación para cargar/descargar ensamblados externos

  • Crear nuevo dominio de aplicación
  • Carga algunas asambleas en él
  • Haga un poco de magia con DLL cargadas
  • Unload dominio de aplicación ponga en libertad memoria & bibliotecas cargadas

A continuación se muestra el código que estoy tratando de usar

class Program 
{ 
    static void Main(string[] args) 
    { 
     Evidence e = new Evidence(AppDomain.CurrentDomain.Evidence); 
     AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation; 
     Console.WriteLine("Creating new AppDomain"); 
     AppDomain newDomain = AppDomain.CreateDomain("newDomain", e, setup); 
     string fullName = Assembly.GetExecutingAssembly().FullName; 
     Type loaderType = typeof(AssemblyLoader); 
     var loader = (AssemblyLoader)newDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap(); 
     Console.WriteLine("Loading assembly"); 
     Assembly asm = loader.LoadAssembly("library.dll"); 
     Console.WriteLine("Creating instance of Class1"); 
     object instance = Activator.CreateInstance(asm.GetTypes()[0]); 
     Console.WriteLine("Created object is of type {0}", instance.GetType()); 
     Console.ReadLine(); 
     Console.WriteLine("Unloading AppDomain"); 
     instance = null; 
     AppDomain.Unload(newDomain); 
     Console.WriteLine("New Domain unloaded"); 
     Console.ReadLine(); 
    } 

    public class AssemblyLoader : MarshalByRefObject 
    { 
     public Assembly LoadAssembly(string path) 
     { 
      return Assembly.LoadFile(path); 
     } 
    } 
} 

library.dll se compone sólo de una sola clase ficticia, con una gran tabla de cadenas (para el seguimiento más fácil el consumo de memoria)

Ahora el problema es que la memoria en realidad no es liberado. Lo que es más sorprendente, el uso de la memoria en realidad aumenta después de AppDomain.Unload()

¿Alguien puede arrojar algo de luz sobre este tema?

Respuesta

1

Cada conjunto se carga en el dominio principal también. Como usa la instancia de conjunto, su dominio principal carga este conjunto para poder analizar todos los tipos en él.

Si desea evitar la carga del ensamblaje en ambos dominios, utilice el método AppDomain.CreateInstance.

7

Esta no es una respuesta completa: Acabo de notar que utiliza una cadena como carga útil. Las cadenas no son útiles para esto, ya que las cadenas literales se internan. Las cadenas internas se comparten entre AppDomains, por lo que esa parte no se descarga cuando descarga su AppDomain. Intenta usar un byte [] en su lugar.

+0

¿Podría elaborar un poco más? ¿A qué línea de código se refiere en la publicación de OP? – NGambit

+0

Supongo que se refiere a devolver Assembly.LoadFile (ruta); Y en su lugar, se recomienda algo como byte [] bytes = File.ReadAllBytes (ruta); return assembly = Assembly.Load (bytes); ¿Lo estoy entendiendo correctamente? – NGambit

3

.Net utiliza una finalización no determinista. Si desea ver si la memoria cae, debe hacer ...

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

... después de la descarga. Además, a menos que tenga una necesidad de forzar la recopilación (más bien improbable), debe permitir que el sistema se recopile por sí mismo. Normalmente, si se siente la necesidad de forzar recogida en el código de producción no es una pérdida de recursos por lo general causada por no llamar a Dispose en objetos IDisposable o por no liberar objetos no administrados

using (var imdisposable = new IDisposable()) 
{ 
} 
// 
var imdisposable = new IDisposable(); 
imdisposable.Dispose(); 
// 
Marshal.Release(intPtr); 
// 
Marshal.ReleaseComObject(comObject); 
1

En realidad, la combinación de las respuestas anteriores me señaló a (I espero) respuesta correcta: Mi código es ahora de la siguiente manera:

AppDomain newDomain = AppDomain.CreateDomain("newDomain", e, setup); 
string fullName = Assembly.GetExecutingAssembly().FullName; 
Type loaderType = typeof(AssemblyLoader); 
FileStream fs = new FileStream(@"library.dll", FileMode.Open); 
byte[] buffer = new byte[(int)fs.Length]; 
fs.Read(buffer, 0, buffer.Length); 
fs.Close(); 

Assembly domainLoaded = newDomain.Load(buffer); 
object loaded = Activator.CreateInstance(domainLoaded.GetTypes()[1]); 
AppDomain.Unload(newDomain); 
GC.Collect(); 
GC.WaitForPendingFinalizers(); 

no puedo usar AppDomain.CreateInstance, ya que requiere Assembly.FullName la que no sé - biblioteca se carga dinámicamente.

Gracias por la ayuda, Bolek.

+1

¿Pero esto funciona? Parece que todavía estás tirando de la asamblea de vuelta a tu dominio de aplicación principal ... –

+1

En realidad, Lasse, tienes razón. No funciona Se libera algo de memoria, pero los manejadores de archivos para archivos dlls cargados no ... Todavía buscando la respuesta ... –

5

Respondiendo a mi propia pregunta - no sé si hay una mejor manera de hacerlo en StackOverflow ... Si hay, estaría agradecido por las instrucciones ... De todos modos, cavando a través de Internet encontré otra solución, que espero sea mejor Código a continuación, si alguien encuentra puntos débiles, por favor responda.

class Program 
{ 
    static void Main(string[] args) 
    { 
     Console.ReadLine(); 
     for(int i=0;i<10;i++) 
     { 
      AppDomain appDomain = AppDomain.CreateDomain("MyTemp"); 
      appDomain.DoCallBack(loadAssembly); 
      appDomain.DomainUnload += appDomain_DomainUnload; 

      AppDomain.Unload(appDomain);   
     } 

     AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2"); 
     appDomain2.DoCallBack(loadAssembly); 
     appDomain2.DomainUnload += appDomain_DomainUnload; 

     AppDomain.Unload(appDomain2); 

     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     Console.ReadLine(); 
    } 

    private static void loadAssembly() 
    { 
     string fullPath = @"E:\tmp\sandbox\AppDomains\AppDomains1\AppDomains1\bin\Debug\BigLib.dll"; 
     var assembly = Assembly.LoadFrom(fullPath); 
     var instance = Activator.CreateInstance(assembly.GetTypes()[0]); 
     Console.WriteLine("Creating instance of {0}", instance.GetType()); 
     Thread.Sleep(2000); 
     instance = null; 
    } 

    private static void appDomain_DomainUnload(object sender, EventArgs e) 
    { 
     AppDomain ap = sender as AppDomain; 
     Console.WriteLine("Unloading {0} AppDomain", ap.FriendlyName); 
    } 
} 
4

Ésta es una respuesta tardía, pero valdría la pena tenerlo aquí para cualquier futuras vistas a esta pregunta. Necesitaba implementar algo similar a esto, pero en una forma de compilación/ejecución de código dinámico. Lo mejor sería ejecutar todos los métodos en un dominio separado, es decir, dominio remoto, que no sea el dominio de aplicación principal, de lo contrario, la memoria de la aplicación siempre aumentará y aumentará. Puede resolver este problema a través de interfaces y proxies remotos. Así expondrías tus métodos a través de una interfaz de la que obtendrás una instancia en tu AppDomain principal y luego ejecutarás remotamente esos métodos en el dominio remoto, descargarás el dominio recién creado (dominio remoto), lo anularás y luego forzarás al GC a recoger objetos no utilizados. Pasé bastante tiempo depurando mi código hasta que me di cuenta de que tenía que forzar al GC a hacerlo y funciona muy bien. A granel de mi implementación se toma desde: http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm.

 //pseudo code 
     object ExecuteCodeDynamically(string code) 
     { 
     Create AppDomain my_app 
     src_code = "using System; using System.Reflection; using RemoteLoader; 
     namespace MyNameSpace{ 
     public class MyClass:MarshalByRefObject, IRemoteIterface 
     { 
     public object Invoke(string local_method, object[] parameters) 
     { 
     return this.GetType().InvokeMember(local_method, BindingFlags.InvokeMethod, null, this, parameters); 
    } 
    public object ExecuteDynamicCode(params object[] parameters) 
    { 
    " + code + } } } ";// this whole big string is the remote application 

    //compile this code which is src_code 
    //output it as a DLL on the disk rather than in memory with the name e.g.: DynamicHelper.dll. This can be done by playing with the CompileParameters 
    // create the factory class in the secondary app-domain 
       RemoteLoader.RemoteLoaderFactory factory = 
        (RemoteLoader.RemoteLoaderFactory)loAppDomain.CreateInstance("RemoteLoader", 
        "RemoteLoader.RemoteLoaderFactory").Unwrap(); 

      // with the help of this factory, we can now create a real instance 
      object loObject = factory.CreateInstance("DynamicHelper.dll", "MyNamespace.MyClass", null); 

      // *** Cast the object to the remote interface to avoid loading type info 
      RemoteLoader.IRemoteInterface loRemote = (RemoteLoader.IRemoteInterface)loObject; 

      if (loObject == null) 
      { 
       System.Windows.Forms.MessageBox.Show("Couldn't load class."); 
       return null; 
      } 

      object[] loCodeParms = new object[1]; 
      loCodeParms[0] = "bla bla bla"; 

      try 
      { 
       // *** Indirectly call the remote interface 
       object result = loRemote.Invoke("ExecuteDynamicCode", loCodeParms);// this is the object to return     

      } 
      catch (Exception loError) 
      { 
       System.Windows.Forms.MessageBox.Show(loError.Message, "Compiler Demo", 
        System.Windows.Forms.MessageBoxButtons.OK, 
        System.Windows.Forms.MessageBoxIcon.Information); 
       return null; 
      } 

      loRemote = null; 
      try { AppDomain.Unload(my_app); } 
      catch (CannotUnloadAppDomainException ex) 
      { String str = ex.Message; } 
      loAppDomain = null; 
      GC.Collect();//this will do the trick and free the memory 
      GC.WaitForPendingFinalizers(); 
      System.IO.File.Delete("ConductorDynamicHelper.dll"); 
      return result; 

} 

Tenga en cuenta que RemoteLoader es otro DLL que debe ser creado ya y se añade a la vez que la aplicación principal y su aplicación de control remoto. Básicamente es una interfaz y un cargador de fábrica. El código siguiente es tomado de la página web anterior:

 /// <summary> 
    /// Interface that can be run over the remote AppDomain boundary. 
    /// </summary> 
    public interface IRemoteInterface 
    { 
    object Invoke(string lcMethod,object[] Parameters); 
    } 


    naemspace RemoteLoader{ 
    /// <summary> 
    /// Factory class to create objects exposing IRemoteInterface 
    /// </summary> 
    public class RemoteLoaderFactory : MarshalByRefObject 
{ 
    private const BindingFlags bfi = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance; 

public RemoteLoaderFactory() {} 

/// <summary> Factory method to create an instance of the type whose name is specified, 
    /// using the named assembly file and the constructor that best matches the specified parameters. </summary> 
/// <param name="assemblyFile"> The name of a file that contains an assembly where the type named typeName is sought. </param> 
/// <param name="typeName"> The name of the preferred type. </param> 
/// <param name="constructArgs"> An array of arguments that match in number, order, and type the parameters of the constructor to invoke, or null for default constructor. </param> 
/// <returns> The return value is the created object represented as ILiveInterface. </returns> 
public IRemoteInterface Create(string assemblyFile, string typeName, object[] constructArgs) 
{ 
    return (IRemoteInterface) Activator.CreateInstanceFrom(
    assemblyFile, typeName, false, bfi, null, constructArgs, 
    null, null, null).Unwrap(); 
    } 
    } 
    } 

la esperanza que esto tiene sentido y ayuda ...

Cuestiones relacionadas