You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core 6 WebAPI使用AssemblyLoadContext加载第三方DLL频繁卸载问题求助

.NET Core 6 WebAPI第三方DLL自动卸载问题分析与解决

一、DLL被卸载的可能原因

  • AssemblyLoadContext实例被GC回收:你当前的代码每次调用LoadPlugin都会新建一个CustomAssemblyLoadContext,但如果没有保留对这个实例的强引用,闲置时GC会把它回收,连带卸载对应的程序集——这是最常见的原因。
  • IIS应用池自动回收:WebAPI闲置超过默认20分钟时,IIS会触发应用池回收,整个应用重启,所有加载的程序集都会被卸载,重启IIS后恢复就是这个原因的典型表现。
  • 依赖解析失败:第三方DLL如果依赖其他未正确加载的程序集,会导致加载上下文无法维持,间接触发卸载。
  • 自定义ALC实现缺陷:如果你的CustomAssemblyLoadContext没有正确重写Load或LoadUnmanagedDll方法,依赖找不到会引发加载异常或后续卸载。

二、LoadFromAssemblyName的问题与解决

LoadFromAssemblyName本身没有致命问题,但使用时踩坑点不少:

  • 依赖路径限制:它会从应用基目录和系统路径查找程序集,要是你的DLL不在这些路径,要么加载失败,要么后续因为依赖缺失触发卸载。
  • ALC生命周期管理:必须保留ALC实例的强引用,否则GC会自动回收并卸载程序集。

解决办法:

  1. 全局存储ALC实例:用静态集合保存所有创建的CustomAssemblyLoadContext,避免被GC回收:
private static readonly Dictionary<string, CustomAssemblyLoadContext> _loadedContexts = new();

public Assembly LoadPlugin(string assemblyPath)
{
    if (_loadedContexts.TryGetValue(assemblyPath, out var context))
    {
        return context.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(assemblyPath)));
    }
    
    var newContext = new CustomAssemblyLoadContext(assemblyPath);
    _loadedContexts.Add(assemblyPath, newContext);
    return newContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(assemblyPath)));
}
  1. 修复自定义ALC的依赖解析:重写Load和LoadUnmanagedDll方法,让它从指定路径加载依赖,同时把isCollectible设为false(避免自动回收):
public class CustomAssemblyLoadContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _resolver;

    public CustomAssemblyLoadContext(string assemblyPath) : base(isCollectible: false)
    {
        _resolver = new AssemblyDependencyResolver(assemblyPath);
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
        return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
    }

    protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
    {
        var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
        return libraryPath != null ? LoadUnmanagedDllFromPath(libraryPath) : IntPtr.Zero;
    }
}
  1. 调整IIS应用池配置:修改应用池的闲置超时时间(比如延长到几小时),或者根据业务需求禁用闲置回收(生产环境谨慎操作)。

三、替代加载方法

如果不想用LoadFromAssemblyName,可以试试这些方案:

  • LoadFromAssemblyPath:直接从文件路径加载,跳过依赖查找步骤,更可靠:
public Assembly LoadPlugin(string assemblyPath)
{
    if (!_loadedContexts.TryGetValue(assemblyPath, out var context))
    {
        context = new CustomAssemblyLoadContext(assemblyPath);
        _loadedContexts.Add(assemblyPath, context);
    }
    return context.LoadFromAssemblyPath(assemblyPath);
}
  • LoadFromStream:如果DLL是从流读取(比如嵌入式资源),可以用这个方法,但要确保流在加载完成前不关闭。
  • 不推荐AppDomain.Load:.NET Core中AppDomain功能被阉割,无法单独卸载程序集,不适合插件场景。

关键提醒

  • 必须保留ALC的强引用:这是防止程序集被卸载的核心,只要ALC有引用,GC就不会碰它。
  • 慎用Collectible ALC:除非你明确需要动态卸载程序集,否则别把isCollectible设为true——这种ALC不仅容易被回收,还可能引发性能和兼容性问题。
  • 搞定依赖问题:第三方DLL的所有依赖必须能被正确加载,否则加载上下文会不稳定,迟早被卸载。

内容的提问来源于stack exchange,提问作者Somnath Pandey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 04:38:23