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

我的方案如下:

  • 创建新的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()之后,内存使用量实际上增加了

任何人都可以对这个问题有所了解吗?

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

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

回答我自己的问题 – 不知道是否有更好的方法在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); } } 

这是一个迟到的答案,但是对于今后对这个问题的任何看法都是值得的。 我需要实现与此类似的东西,但是采用动态代码编译/执行方式。 最好的方法是在单独的域中执行所有方法,即:远程域,而不是主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 eg: 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。 它基本上是一个接口和一个工厂装载器。 以下代码取自上述网站:

  ///  /// Interface that can be run over the remote AppDomain boundary. ///  public interface IRemoteInterface { object Invoke(string lcMethod,object[] Parameters); } naemspace RemoteLoader{ ///  /// Factory class to create objects exposing IRemoteInterface ///  public class RemoteLoaderFactory : MarshalByRefObject { private const BindingFlags bfi = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance; public RemoteLoaderFactory() {} ///  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.  ///  The name of a file that contains an assembly where the type named typeName is sought.  ///  The name of the preferred type.  ///  An array of arguments that match in number, order, and type the parameters of the constructor to invoke, or null for default constructor.  ///  The return value is the created object represented as ILiveInterface.  public IRemoteInterface Create( string assemblyFile, string typeName, object[] constructArgs ) { return (IRemoteInterface) Activator.CreateInstanceFrom( assemblyFile, typeName, false, bfi, null, constructArgs, null, null, null ).Unwrap(); } } } 

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

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

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

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

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

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

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

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

 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。

您可以尝试以下代码: http : //www.west-wind.com/presentations/dynamicCode/DynamicCode.htm