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

Roslyn导致内存泄漏,如何在.NET6.0 Blazor项目中解决?

问题解决:.NET6中Roslyn脚本编译导致内存溢出及AssemblyLoadContext加载异常

问题背景

基于.NET6的控制台应用作为UDP消息桥接工具,通过读取十余份配置文件生成方法字符串,使用Microsoft.CodeAnalysis.CSharp.Scripting包编译为ScriptRunner<string>委托执行。添加Blazor Web界面并引用原项目后,启动时出现内存溢出,排查确认是Roslyn编译生成的程序集无法卸载所致;尝试使用可收集的AssemblyLoadContext卸载程序集时,触发System.IO.FileLoadException,提示无法加载System.Private.CoreLib。

错误原因分析

  1. 系统程序集加载冲突:手动通过context.LoadFromAssemblyPath(typeof(object).Assembly.Location)加载System.Private.CoreLib,但该程序集属于.NET系统加载上下文,无法被自定义AssemblyLoadContext加载,强行加载会触发文件加载异常。
  2. ALC卸载时机错误:创建委托后立即卸载ALC,而委托持有对ALC中程序集的引用,既无法完成有效卸载,还会导致程序集引用状态混乱。
  3. 全局类型加载问题:globalsType所在的程序集未被正确纳入自定义ALC的解析范围,导致脚本编译时无法找到依赖类型。

解决方案

1. 正确配置自定义AssemblyLoadContext

避免手动加载系统程序集,让ScriptOptions自动处理系统依赖,同时确保全局类型所在程序集被正确加载到ALC中:

// 用于关联存储ALC与对应的脚本委托,方便后续统一管理
private readonly Dictionary<string, (AssemblyLoadContext Alc, ScriptRunner<string> Delegate)> _scriptDelegateCache = new();

public void BuildScriptDelegate(string configKey, string scriptCode, Type globalsType)
{
    // 创建可收集的ALC,每个脚本使用独立上下文避免交叉污染
    var alc = new AssemblyLoadContext($"Script_{configKey}", isCollectible: true);
    
    // 将全局类型所在的程序集加载到当前ALC
    var targetAssembly = alc.LoadFromAssemblyPath(globalsType.Assembly.Location);
    var alcGlobalsType = targetAssembly.GetType(globalsType.FullName);

    // 配置脚本选项:添加必要引用和命名空间
    var scriptOptions = ScriptOptions.Default
        .WithReferences(targetAssembly)
        .WithImports("System", globalsType.Namespace); // 根据脚本需求添加命名空间

    // 编译脚本并生成委托
    var script = CSharpScript.Create<string>(scriptCode, globalsType: alcGlobalsType, options: scriptOptions);
    var scriptRunner = script.CreateDelegate();

    // 将ALC和委托存入缓存
    _scriptDelegateCache[configKey] = (alc, scriptRunner);
}

2. 合理管理ALC卸载时机

必须在脚本委托不再使用后,先释放委托引用,再触发ALC卸载,确保GC能回收相关程序集:

public void UnloadScriptDelegate(string configKey)
{
    if (_scriptDelegateCache.TryRemove(configKey, out var cacheEntry))
    {
        // 先释放委托引用,切断对ALC程序集的持有
        cacheEntry.Delegate = null;
        // 触发ALC卸载
        cacheEntry.Alc.Unload();
        
        // 触发一次GC(可选,加速内存回收)
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

3. 额外优化建议

  • 缓存重复编译结果:如果配置文件内容未变更,直接复用已编译的委托,避免重复编译产生大量临时程序集。
  • 监控程序集加载状态:使用.NET诊断工具(如dotnet-dump)查看程序集加载情况,确认自定义ALC是否被正确回收。
  • 适配Blazor环境:Blazor Server模式下,避免在请求线程中频繁创建ALC;Blazor WebAssembly模式下,需注意程序集加载的沙箱限制,优先复用已加载的程序集。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 13:43:16