【问题标题】:Use AppDomain to load/unload external assemblies使用 AppDomain 加载/卸载外部程序集
【发布时间】:2009-11-06 12:22:21
【问题描述】:

我的场景如下:

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

谁能解释一下这个问题?

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    这不是一个完整的答案:我刚刚注意到您使用字符串作为有效负载。字符串对此没有用处,因为文字字符串是实习的。实习字符串在 AppDomain 之间共享,因此在您卸载 AppDomain 时不会卸载该部分。尝试使用 byte[] 代替。

    【讨论】:

    • 您能详细说明一下吗?您指的是 OP 帖子中的哪一行代码?
    • 我假设您指的是 return Assembly.LoadFile(path);并推荐类似 byte[] bytes = File.ReadAllBytes(path);返回程序集 = Assembly.Load(bytes);我理解正确吗?
    【解决方案2】:

    回答我自己的问题 - 不知道在 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】:

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

      【讨论】:

      【解决方案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();
         }
        }
        }
      

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

      【讨论】:

        【解决方案5】:

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

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

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

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

        【讨论】:

          【解决方案6】:

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

          如果您想防止在两个域中加载程序集 - 使用 AppDomain.CreateInstance 方法。

          【讨论】:

            【解决方案7】:

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

            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 - 库是动态加载的。

            感谢您的帮助, 博莱克。

            【讨论】:

            • 但这行得通吗?看来您仍在将程序集拉回您的主应用程序域给我...
            • 其实Lasse,你是对的。它不起作用。一些内存被释放了,但加载的 dll 的文件句柄没有......仍在寻找答案......
            【解决方案8】:

            【讨论】:

              猜你喜欢
              • 2012-11-06
              • 2011-01-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-12
              • 2021-09-11
              相关资源
              最近更新 更多