在运行时加载、使用和卸载程序集

Posted

技术标签:

【中文标题】在运行时加载、使用和卸载程序集【英文标题】:Load, Use and the Unload an Assembly at runtime 【发布时间】:2011-12-01 16:34:14 【问题描述】:

我正在做一个项目,我必须在运行时加载程序集(让我们称之为任务),运行任务然后能够终止整个程序集,以便可以在不中断主应用程序的情况下替换 dll。

在主应用程序中运行着许多这样的任务,有些是顺序运行的,有些是并行运行的。有时需要更新其中一项或多项任务,然后将其重新添加到队列中。目前我们正在停止整个应用程序并在其他任务处于任何阶段时中断它们,这并不理想。

我发现我必须将每个程序集加载到单独的 AppDomain 中,但事实证明在这些域中加载程序集很困难,尤其是当我需要实际运行任务并从它们接收事件时。我已经研究这个问题几天了,但仍然没有设法得到一个有效的概念证明。

我有一个用于任务的接口,其中包括一个“运行”和“杀死”方法(子)以及一个“任务步骤”、“完成”和“杀死”事件。 “taskstep”返回一个稍后缓存的对象,“完成”在整个任务完成时触发,“killed”在准备卸载时触发。整个过程也应该有两个小时的超时时间,并且在被杀死的事件上也应该有 2 分钟的超时时间,以防它被卡住,此时我希望能够卸载它,强制任何线程终止(这是无论如何“杀”应该做什么)。每个程序集可能包含多个任务,所有这些任务都应该是可加载的。

我将这些任务作为“插件”加载没有问题,但在尝试使用它们和卸载它们时我会迷路。如果我必须创建一些精心制作的包装器,那就这样吧,但我需要的东西可能吗?

我尝试从 MarshalByRefObject 继承,但我什至不知道程序集全名,除非我先加载它,然后锁定文件。我尝试从程序集的字节数组中加载。这意味着该文件未锁定,但它的副本仍保留在当前应用程序域中。这将在接下来的几个月/几年内成为问题!

For Each key As String In assemblies.Keys

    Dim ad As AppDomain = AppDomainHelper.BuildChildAppDomain(AppDomain.CurrentDomain, key)

    AddHandler ad.AssemblyResolve, AddressOf AssemblyResolve

    _l.Add(ad)

    For Each value As String In assemblies(key)
        Dim item As IScanner = CType(ad.CreateInstanceAndUnwrap(key, value), IScanner)
        ListBox1.Items.Add(item)
    Next
Next

Private Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
    Return GetType(IScanner).Assembly
End Function

【问题讨论】:

在自己的应用程序域中加载每个程序集似乎有什么问题?这是我过去采取的方法。 我不知道 Assembly.fullname 直到运行时才加载它。当我只知道它的文件路径时,如何将程序集加载到它自己的 appdomain 中,谢谢你的回复。 System.Reflection.AssemblyName.GetAssemblyName(路径) 太好了!非常感谢 现在我有了程序集名称(谢谢 Boo),但我很难加载扫描仪(请参阅我编辑的代码)。程序集无法解决“ResolveEventArgs 不可序列化” 【参考方案1】:

考虑使用Managed Extensibility Framework。

它是 .Net 4.0 的一部分,您可以阅读简短的概述 here。

加载和卸载程序集可能是一个非常糟糕的主意 - 事情并不像您预期​​的那样工作(曾经尝试捕获另一个 AppDomain 中引发的异常?BAM 你不能!)

【讨论】:

【参考方案2】:

似乎从新的(辅助的,如果你愿意的话)AppDomain 引发了异常。这意味着必须对 ResolveEventArgs 进行序列化才能跨越 appdomain 边界。您应该从辅助 appdomain 中处理事件,这样您就不必跨越该边界。

所以...创建一个处理分辨率的类...类似于...

Public Class AssemblyResolver
  Inherits MarshalByRefObject

  Public Property SearchPath As String = String.Empty

  Public Function ResolveAssembly(sender as Object, e As ResolveEventArgs) As Assembly

    Dim tPath As String = Path.Combine(SearchPath, e.Name & ".dll")

    If File.Exists(tPath) Then
      Return Assembly.LoadFrom(tPath)
    End If

    Return Nothing

  End Function

End Class

现在从你的插件加载器中使用它...

Dim tPluginDomain As AppDomain = AppDomainHelper.BuildChildAppDomain(AppDomain.CurrentDomain, key)
Dim tResolver As AssemblyResolver = tPluginDomain.CreateInstanceAndUnwrap(GetType(AssemblyResolver).Assembly.FullName, GetType(AssemblyResolver).FullName)
AddHandler tPluginDomain.AssemblyResolve, AddressOf tResolver.ResolveAssembly

这应该会让你找到正确的方向。 参考 MSDN,所有代码都在我脑海中。不知道它是否会编译.. 希望错字/调试它。

编辑 哎呀..忘记将解析器标记到插件域中。固定。

【讨论】:

以上是关于在运行时加载、使用和卸载程序集的主要内容,如果未能解决你的问题,请参考以下文章

C#动态加载dll 时程序集的卸载问题

卸载 appdomain 后程序集仍然存在

完整的动态加载卸载程序集的解决方案

如何卸载已加载的程序集

通过应用程序域AppDomain加载和卸载程序集

C#.Net 如何动态加载与卸载程序集(.dll或者.exe)