使用AppDomain加载/卸载外部程序集

时间:2009-11-06 12:22:22

标签: c# .net

我的方案如下:

  • 创建新的AppDomain
  • 将一些组件加载到其中
  • 用加载的dll做一些魔法
  • 卸载AppDomain以释放内存&已加载的库

以下是我正在尝试使用的代码

    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 只包含一个虚拟类,带有一个巨大的字符串表(以便更轻松地跟踪内存消耗)

现在的问题是内存实际上没有被释放。更令人惊讶的是,在AppDomain.Unload()

之后内存使用率实际上会增加

有人能解释一下这个问题吗?

8 个答案:

答案 0 :(得分:7)

这不是一个完整的答案:我只是注意到你使用一个字符串作为有效载荷。字符串对此没有用,因为字符串是实例化的。 Interned字符串在AppDomains之间共享,因此卸载AppDomain时不会卸载该部分。尝试使用byte []代替。

答案 1 :(得分:6)

我发布了一个示例,其中3个不同的程序集在不同的应用程序域中加载并成功卸载。这是链接http://www.softwareinteractions.com/blog/2010/2/7/loading-and-unloading-net-assemblies.html

答案 2 :(得分:5)

回答我自己的问题 - 不知道是否有更好的方法在StackOverflow上做...如果有,我会感激指示...... 无论如何,通过互联网挖掘我发现了另一种解决方案,我希望它更好。 以下代码,如果有人发现任何弱点 - 请回复。

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

答案 3 :(得分:4)

这是一个迟到的答案,但是对于今后对此问题的任何看法都值得。我需要实现与此类似的东西,但是以动态代码编译/执行方式。 最好的方法是在一个单独的域中执行所有方法,即:远程域,而不是主AppDomain,否则应用程序内存将始终增加和增加。您可以通过远程接口和代理解决此问题。 因此,您将通过一个接口公开您的方法,您将获得主AppDomain中的实例,然后在远程域中远程执行这些方法,卸载新创建的域(远程域),使其无效,然后强制GC收集未使用的物体我花了很长时间调试我的代码,直到我放心,我必须强制GC这样做,它工作得很好。我的实施大部分来自: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;

}

请注意,RemoteLoader是另一个应该已经创建并添加到主应用程序和远程应用程序的DLL。它基本上是一个接口和工厂加载器。以下代码取自上述网站:

      /// <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();
   }
  }
  }

希望这有意义并有所帮助......

答案 4 :(得分:3)

.Net使用非确定性终结。如果你想看看内存是否下降,你应该做...

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

...卸载后。此外,除非您需要强制收集(相当不可能),否则您应该允许系统自行收集。通常,如果您觉得需要在生产代码中强制收集,则通常由于不在IDisposable对象上调用Dispose或者不释放非托管对象而导致资源泄漏

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

答案 5 :(得分:1)

每个程序集也会加载到主域中。由于您使用Assembly实例,因此您的主域将加载此程序集,以便能够分析其中的所有类型。

如果要阻止在两个域中加载程序集 - 请使用AppDomain.CreateInstance方法。

答案 6 :(得分:1)

实际上,上述答案的组合使我(我希望)正确回答: 我的代码现在如下:

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();

我无法使用AppDomain.CreateInstance,因为它需要我不知道的Assembly.FullName - 库是动态加载的。

感谢您的帮助, Bolek。

答案 7 :(得分:1)