C# AssemblyLoadContext使用方法 C#如何隔离和卸载插件

来源:这里教程网 时间:2026-02-21 17:40:58 作者:

AssemblyLoadContext 能否真正卸载程序集

不能直接卸载,但可以卸载整个

AssemblyLoadContext
实例及其加载的所有程序集——前提是该上下文是“可卸载的”(
isCollectible = true
),且其中没有强引用、静态字段、线程或 Finalizer 持有对象。.NET Core 3.0+ 支持此机制,.NET Framework 不支持。

常见错误现象:

AssemblyLoadContext.Unload()
返回后,内存未释放、类型仍可访问、GC 不回收上下文对象。

必须在构造时传入
true
:new
AssemblyLoadContext
(true)
不能在默认上下文(
AssemblyLoadContext.Default
)中加载插件——它不可卸载
插件内不能调用
typeof(...).Assembly
并跨上下文缓存类型,否则会隐式持有引用
插件导出的接口类型必须定义在主程序集(或共享的、由默认上下文加载的程序集)中

如何安全加载和调用插件类型

核心原则:所有跨上下文交互必须通过接口(而非具体类型),且接口定义必须由主程序集提供。插件只负责实现,不参与类型定义。

使用场景:动态加载 DLL 插件,执行某方法后卸载,避免程序集锁定和内存泄漏。

主程序定义公共接口,如
IPlugin
void Execute()
插件项目引用主程序集(仅用于编译,运行时不复制) 用自定义
AssemblyLoadContext
加载插件 DLL:
var context = new AssemblyLoadContext(pluginPath, isCollectible: true);
var assembly = context.LoadFromAssemblyPath(pluginPath);
var pluginType = assembly.GetType("MyPlugin");
var plugin = Activator.CreateInstance(pluginType) as IPlugin;
调用完成后,显式调用
context.Unload()
,然后等待 GC 回收(可手动
GC.Collect(); GC.WaitForPendingFinalizers();
辅助验证)

为什么插件里 new Thread 或 Timer 会导致卸载失败

因为线程、定时器回调、事件订阅、静态 Action/Func 等都会在后台维持对插件上下文中类型的强引用,使

AssemblyLoadContext
无法被 GC 回收。

典型错误代码:

public class MyPlugin : IPlugin {
    private Timer _timer;
    public void Execute() {
        _timer = new Timer(_ => { Console.WriteLine("tick"); }, null, 0, 1000);
    }
}

Timer 回调委托捕获了插件实例,而该实例类型来自插件上下文,导致上下文无法卸载 解决方案:在
Execute()
前或
Unload()
前主动
_timer?.Dispose()
更稳妥做法:插件实现
IDisposable
,主程序在
Unload()
前先调用
plugin.Dispose()
避免在插件中启动长期存活线程;如需后台任务,改用主程序托管的
Task
+ 取消令牌

调试卸载失败的常见手段

context.Unload()
后上下文仍存活,说明仍有根引用未清理。这不是黑盒问题,有明确排查路径。

用 Visual Studio 的“调试 → Windows → .NET Object Allocation Tracking”观察上下文对象是否被回收 用 dotMemory 或 dotTrace 快照比对:卸载前后搜索
AssemblyLoadContext
实例数量
检查插件中是否用了
AppDomain.CurrentDomain.AssemblyResolve
或自定义
AssemblyLoadContext.Default.Load
回调——这些委托可能跨上下文泄露
确保插件 DLL 没被其他地方(如
Assembly.GetExecutingAssembly()
、日志框架反射扫描)意外加载进默认上下文

最常被忽略的一点:插件中的

static readonly Dictionary<type object></type>
缓存,只要键值是插件上下文里的
Type
,就会阻止整个上下文回收。

相关推荐