【问题标题】:How to unload an assembly from the primary AppDomain?如何从主 AppDomain 卸载程序集?
【发布时间】:2010-09-12 12:15:02
【问题描述】:

我想知道如何卸载加载到主 AppDomain 中的程序集。

我有以下代码:

var assembly = Assembly.LoadFrom( FilePathHere );

完成后我需要/希望能够卸载此程序集。

感谢您的帮助。

【问题讨论】:

  • 尽量不要使用 LoadFrom,它与 Load 上下文处于不同的上下文中,可能会导致问题。
  • 好问题,但我看不到任何关于如何解决 var assembly = Assembly.LoadFrom( FilePathHere ); 的明确答案

标签: c# .net appdomain


【解决方案1】:

对于 .net 版本的 core 3.0 及更高版本:

您现在可以卸载程序集。请注意,应用程序域在 .net 核心中不再可用。相反,您可以创建一个或多个 AssemblyLoadContext,通过该上下文加载程序集,然后卸载该上下文。请参阅AssemblyLoadContextthis tutorial that simulates loading a plugin then unloading it

适用于 .net core 3 之前的 .net 版本,包括网络框架 4 及更低版本

您不能从 appdomain 中卸载程序集。您可以销毁应用程序域,但是一旦将程序集加载到应用程序域中,它就会在应用程序域的整个生命周期内一直存在。

查看 Jason Zander 对Why isn't there an Assembly.Unload method?的解释

如果您使用的是 3.5,则可以使用 AddIn 框架来更轻松地管理/调用不同的 AppDomain(您可以卸载它们,卸载所有程序集)。如果您使用的是之前的版本,您需要自己创建一个新的 appdomain 来卸载它。

【讨论】:

  • 我认为 .Net 3.5 改变了这种情况?
  • 没有计划允许从应用程序域卸载程序集(我也不希望他们这样做,这很难做到,那些关心的人会使用更安全、更清洁的解决方法)。你能做的最好的就是 Khoth 提到的轻量级代码生成
【解决方案2】:

我也知道这已经很老了,但可以帮助遇到这个问题的人! 这是我发现的一种方法! 而不是使用:

var assembly = Assembly.LoadFrom( FilePathHere );

使用这个:

var assembly = Assembly.Load( File.ReadAllBytes(FilePathHere));

这实际上加载了程序集文件的“内容”,而不是文件本身。这意味着程序集文件上没有文件锁!所以现在它可以被复制、删除或升级,而无需关闭您的应用程序或尝试使用单独的 AppDomain 或编组!

优点: 使用 1 行代码修复非常简单! 缺点:无法使用 AppDomain、Assembly.Location 或 Assembly.CodeBase。

现在您只需要销毁在程序集上创建的所有实例。 例如:

assembly = null;

【讨论】:

  • 无法使用 AppDomain”是指您无法在该程序集中启动一个,或者您可以更具体一点吗? AppDomain.CurrentDomain.Load(File.ReadAllBytes(FilePathHere)); 在这种特定场景下与 Assembly.Load 有什么真正的区别吗?
  • 在编组、新的 AppDomain 等方面苦苦挣扎了大约 1 天。这对我很有用!
  • 需要通信两个程序集的问题 =(
【解决方案3】:

如果不卸载整个 AppDomain,就无法卸载程序集。 Here's why:

  1. 您正在应用程序域中运行该代码。这意味着有可能会继续工作的调用站点和调用堆栈,其中包含地址。

  2. 假设您确实设法跟踪程序集对已运行代码的所有句柄和引用。假设你没有生成代码,一旦你成功释放了程序集,你只释放了元数据和 IL。 JIT 代码仍然分配在应用程序域加载器堆中(JIT 方法按调用顺序在缓冲区中顺序分配)。

  3. 最后一个问题与已加载共享的代码有关,否则更正式地称为“域中立”(查看 ngen 工具上的 /shared)。在这种模式下,生成的程序集代码可以从任何应用程序域执行(没有硬连线)。

建议您围绕应用程序域边界自然地设计应用程序,完全支持卸载。

【讨论】:

    【解决方案4】:

    您应该将临时程序集加载到另一个 AppDomain 中,当不使用时,您可以卸载该 AppDomain。既安全又快捷。

    【讨论】:

    • 查看 AppDomain.DoCallback 并查看 MarshalByRefObject:您创建一个 MarshalByRefObject “invoker”,其方法可以执行您想要的操作并在其可变字段中返回某些内容,您创建一个临时 AppDomain 并调用 dom.DoCallback (invoker.DoSomething) 让这个对象做你想做的事;然后您从应用程序域中的调用程序收集结果并卸载临时域。
    • @jkff:不幸的是,如果您使用 MarshalByRefObject,如 PingPong 示例中,Assembly.LoadFrom 将像在主 AppDomain 中一样执行,因此卸载临时 AppDomain 仍然会产生旧问题能够卸载程序集。我认为您可能必须以某种方式从临时 AppDomain 序列化您想要的数据,而不进行封送处理,如果您想访问它并且仍然能够卸载程序集。
    • @jkff:虽然您可能是对的......但它并不像 MSDN 示例中描述的那么简单。可能应该使用 tempAppDomain.CreateInstanceAndUnwrap...,甚至可能需要使用 AppDomain.GetData 和 SetData。 (如果它的唯一目的是加载和卸载,这不会真正使 appdomain 混乱。)
    【解决方案5】:

    如果您想拥有可以在之后卸载的临时代码,根据您的需要,DynamicMethod 类可能会执行您想要的操作。不过,这并没有给你上课。

    【讨论】:

      【解决方案6】:

      这是一个很好的例子,如何在运行时编译和运行 dll,然后卸载所有资源: http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm

      【讨论】:

        【解决方案7】:

        我知道它很旧,但可能会对某人有所帮助。您可以从流中加载文件并释放它。它对我有用。我找到了解决方案HERE

        希望对你有帮助。

        【讨论】:

          【解决方案8】:

          作为替代方案,如果程序集刚被加载,要检查公共密钥等程序集的信息,更好的方法是不加载它,而是先加载AssemblyName来检查信息:

          AssemblyName an = AssemblyName.GetAssemblyName ("myfile.exe");
          byte[] publicKey = an.GetPublicKey();
          CultureInfo culture = an.CultureInfo;
          Version version = an.Version;
          

          编辑

          如果您需要在程序集中反映类型而不将程序集放入您的应用程序域,您可以使用Assembly.ReflectionOnlyLoadFrom 方法。 这将允许您查看它们在程序集中的类型,但不允许您实例化它们,并且也不会将程序集加载到 AppDomain。

          将此示例视为解释

          public void AssemblyLoadTest(string assemblyToLoad)
          {
              var initialAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //4
          
              Assembly.ReflectionOnlyLoad(assemblyToLoad);
              var reflectionOnlyAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //4
          
              //Shows that assembly is NOT loaded in to AppDomain with Assembly.ReflectionOnlyLoad
              Assert.AreEqual(initialAppDomainAssemblyCount, reflectionOnlyAppDomainAssemblyCount); // 4 == 4
          
              Assembly.Load(assemblyToLoad);
              var loadAppDomainAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Count(); //5
          
              //Shows that assembly is loaded in to AppDomain with Assembly.Load
              Assert.AreNotEqual(initialAppDomainAssemblyCount, loadAppDomainAssemblyCount); // 4 != 5
          }
          

          【讨论】:

          • 这不是真的,虽然程序集不被视为程序集的一部分(因此是GetAssemblies() 调用的结果),但在卸载应用程序域之前它们不会被卸载,请参阅@ 987654321@
          • @yoelhalb 如果您说“这不是真的”,您指的是什么?编辑之前的部分,还是之后的部分?我的答案中没有提到“卸载”? GetAssemblyName() 不会将程序集加载到应用程序域中,因此您不必担心卸载。 ReflectionOnlyLoad() 将其加载到不同的上下文中,在该上下文中代码无法执行,因此您也不需要卸载它。
          • 因为在我的第一条评论中指向 MSDN 的链接指定 ReflectionOnlyLoad 实际上将其加载到 AppDomain 中,只是加载到不允许从中执行代码的不同上下文中,但是直到AppDomain 已卸载,我实际对其进行了测试,这就是它的工作方式。
          猜你喜欢
          • 2012-11-06
          • 1970-01-01
          • 2021-09-11
          • 2011-01-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多