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。
错误原因分析
- 系统程序集加载冲突:手动通过
context.LoadFromAssemblyPath(typeof(object).Assembly.Location)加载System.Private.CoreLib,但该程序集属于.NET系统加载上下文,无法被自定义AssemblyLoadContext加载,强行加载会触发文件加载异常。 - ALC卸载时机错误:创建委托后立即卸载ALC,而委托持有对ALC中程序集的引用,既无法完成有效卸载,还会导致程序集引用状态混乱。
- 全局类型加载问题:
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
相关产品推荐
相关产品推荐

