所以我有这个 NET6.0 控制台应用程序,它充当其他 UDP 消息应用程序之间的桥梁。
要定义如何从我的应用程序发送数据,请使用此配置文件系统,该系统由十几个文件组成,其中定义了每个数据结构的所需变量。在启动时,我读取每个文件,并为文件中定义的每个数据结构编写一个字符串,该字符串表示我想要用来为每个配置文件生成自定义字符串的方法,然后使用 Roslyn 对其进行编译以创建委托以供以后使用(通过Microsoft.CodeAnalysis.CSharp.Scripting NuGet 包):
ScriptRunner<string> scriptToAdd = CSharpScript.Create<string>(toConvert, globalsType: WorldData.GetTypeByName(tempName)).CreateDelegate();
虽然它是一个控制台应用程序,只有很少的配置文件,但它按预期工作,但现在我必须为这个应用程序提供一个 Web 界面(我通过创建 Blazor 项目并引用原始项目来做到这一点),现在在启动时我得到一个 Out Of内存异常。
经过一些研究,我发现 Roslyn 在编译某些代码时会加载所有必要的程序集,但随后不会卸载它们。
我做的第一件事就是验证 Roslyn 确实是罪魁祸首,所以我跳过了委托创建,它就像一个魅力。
下一步是确定我是否可以在 Roslyn 处理完程序集后卸载它们,然后我发现了 AssemblyLoadContext,但由于超出了我的知识范围,我寻求了 GitHub Copilot 的帮助,它给了我这段代码:
var context = new AssemblyLoadContext("ScriptContext", isCollectible: true);
try
{
scriptToAdd = CSharpScript.Create<string>(
toConvert,
globalsType: WorldData.GetTypeByName(tempName),
options: ScriptOptions.Default.WithReferences(
context.LoadFromAssemblyPath(typeof(object).Assembly.Location)
)
).CreateDelegate();
}
finally
{
context.Unload();
}
作为一个相对缺乏经验的开发人员,这看起来很有希望,我知道需要多次调用 LoadFromAssemblyPath 才能获取所需的每个程序集,但我想尝试一下。 然后我得到这个错误:
System.IO.FileLoadException:“无法加载文件或程序集“System.Private.CoreLib,版本= 6.0.0.0,文化=中性,PublicKeyToken = 7cec85d7bea7798e”。”
在这一点上,我已经超出了我的范围,除了这篇文章之外找不到更多的东西,但正如我已经说过的......远远超出了我的范围。
提前感谢任何愿意帮助我的人。让我知道我是否可以以及还可以提供什么来帮助您帮助我。
首先,这不是内存泄漏,这是 Roslyn 的预期工作方式。它以内存使用量(通过尽可能多、尽可能频繁地缓存大量数据)来换取速度,以允许在 VS 中的每次按键时进行近实时编译,包括用户定义的分析器和源代码生成。效果很漂亮!
我遇到内存不足异常
我要检查的第一件事是您的应用程序是否未在 32 位模式下运行。 .Net 应用程序十多年来一直不打算在 32 位模式下运行。在 64 位模式下,您永远不会遇到内存不足的异常。
然后它不会卸载它们
由于 JIT 的工作方式,一旦加载了程序集,您就无法卸载它们。最多你可以通过函数调用来清理数据结构,但 Roslyn 是为了性能而设计的,因此它管理自己的内存并且不提供用户可见的清理函数。
有了这个,如果您仍然想减少总体内存使用量,您实际上有两个选择:
.dll
程序集,然后引用它并像现在一样使用它(使用委托)。这样,当编译器进程终止时,缓存就会消失。