C# 如何防止CompileAssemblyFromSource泄漏内存?
我有一些C代码,它使用CSharpCodeProvider.CompileAsemblyFromSource在内存中创建程序集。在对程序集进行垃圾收集之后,我的应用程序使用的内存比创建程序集之前多。我的代码在ASP.NET web应用程序中,但我在WinForm中复制了此问题。我使用System.GC.GetTotalMemory(true)和Red Gate ANTS内存分析器来测量增长(样本代码约为600字节) 从我所做的搜索来看,泄漏似乎来自新类型的创建,而不是我持有引用的任何对象。我发现的一些网页提到了AppDomain,但我不明白。有人能解释一下这里发生了什么以及如何修复它吗 下面是一些泄漏的示例代码:C# 如何防止CompileAssemblyFromSource泄漏内存?,c#,memory-leaks,marshalling,appdomain,compileassemblyfromsource,C#,Memory Leaks,Marshalling,Appdomain,Compileassemblyfromsource,我有一些C代码,它使用CSharpCodeProvider.CompileAsemblyFromSource在内存中创建程序集。在对程序集进行垃圾收集之后,我的应用程序使用的内存比创建程序集之前多。我的代码在ASP.NET web应用程序中,但我在WinForm中复制了此问题。我使用System.GC.GetTotalMemory(true)和Red Gate ANTS内存分析器来测量增长(样本代码约为600字节) 从我所做的搜索来看,泄漏似乎来自新类型的创建,而不是我持有引用的任何对象。我发现
private void leak()
{
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
parameters.ReferencedAssemblies.Add("system.dll");
string sourceCode = "using System;\r\n";
sourceCode += "public class HelloWord {\r\n";
sourceCode += " public HelloWord() {\r\n";
sourceCode += " Console.WriteLine(\"hello world\");\r\n";
sourceCode += " }\r\n";
sourceCode += "}\r\n";
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, sourceCode);
Assembly assembly = null;
if (!results.Errors.HasErrors)
{
assembly = results.CompiledAssembly;
}
}
更新1:此问题可能与以下方面有关:
更新2:为了进一步了解应用程序域,我发现:
更新3:为了澄清,我正在寻找一种解决方案,该解决方案提供与上述代码相同的功能(编译并提供对生成代码的访问),而不会泄漏内存。看起来解决方案将涉及创建新的AppDomain和封送处理。不支持卸载程序集。可以找到一些关于原因的信息。
可以找到有关使用AppDomain的一些信息。您可能也会发现此博客条目很有用:它提供了一些示例代码,演示如何创建AppDomain,将(动态)程序集加载到其中,在新AppDomain中执行一些工作,然后卸载它
编辑:修复了下面评论中指出的链接。我想我有一个可行的解决方案。感谢大家为我指明了正确的方向(我希望如此) 无法直接卸载程序集,但AppDomains可以。我创建了一个助手库,它可以加载到一个新的AppDomain中,并且能够从代码编译一个新的程序集。下面是该帮助程序库中的类的外观:
public class CompilerRunner : MarshalByRefObject
{
private Assembly assembly = null;
public void PrintDomain()
{
Console.WriteLine("Object is executing in AppDomain \"{0}\"",
AppDomain.CurrentDomain.FriendlyName);
}
public bool Compile(string code)
{
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
parameters.ReferencedAssemblies.Add("system.dll");
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code);
if (!results.Errors.HasErrors)
{
this.assembly = results.CompiledAssembly;
}
else
{
this.assembly = null;
}
return this.assembly != null;
}
public object Run(string typeName, string methodName, object[] args)
{
Type type = this.assembly.GetType(typeName);
return type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, assembly, args);
}
}
这是非常基本的,但足以进行测试。PrintDomain用于验证它是否存在于我的新AppDomain中。Compile获取一些源代码并尝试创建程序集。Run让我们测试从给定源代码执行静态方法
以下是我如何使用帮助器库:
static void CreateCompileAndRun()
{
AppDomain domain = AppDomain.CreateDomain("MyDomain");
CompilerRunner cr = (CompilerRunner)domain.CreateInstanceFromAndUnwrap("CompilerRunner.dll", "AppDomainCompiler.CompilerRunner");
cr.Compile("public class Hello { public static string Say() { return \"hello\"; } }");
string result = (string)cr.Run("Hello", "Say", new object[0]);
AppDomain.Unload(domain);
}
它基本上创建域,创建我的助手类(CompilerRunner)的实例,使用它编译新程序集(隐藏),从新程序集运行一些代码,然后卸载域以释放内存
您将注意到MarshallByRefObject和CreateInstanceFromandWrap的使用。这些对于确保帮助程序库确实存在于新域中非常重要
如果有人注意到任何问题或提出改进建议,我很乐意听取他们的意见。您能等到.NET 4.0吗?使用它,您可以使用表达式树和DLR动态生成代码,而不会出现代码生成器内存丢失问题 另一种选择是将.NET3.5与诸如IronPython之类的动态语言结合使用 编辑:表达式树示例
Jeremy是正确的,无法强制.NET卸载程序集。你将不得不使用appdomain(这有点烦人,但实际上并不是那么糟糕),这样你就可以在完成后转储整个程序。所以,我听说你不能卸载程序集,除非你将它加载到它自己的appdomain中,然后转储整个程序集。这听起来像是一个可行的解决方案,那么如何编译和使用动态代码呢?这似乎是我需要知道的方向。我可以将其与CompileAsEmblyFromSource一起使用吗?我可以从web应用程序创建新的AppDomain吗?您可以尝试在单独的AppDomain中调用CompileAssemblyFromSource,然后在完成后卸载该域。上面的链接应该是:博客条目已被删除,博客条目链接已修复,我喜欢您的建议,但不幸的是,它们在这个项目上对我不起作用(我们还没有使用.NET4,也不能使用IronPython(只有C)。如果你不介意的话,你能把你关于表达式树的答案具体化吗。它可能会帮助其他人。它们可以用来获取存储在字符串中的内容,并将其转换为可以从内存中卸载的工作代码吗?谢谢。我在一篇关于表达式树的文章中添加了一个链接。如果您使用DLR动态创建代码,运行它时会占用空间。你能在没有appdomains的情况下在.NET4中释放此文件吗?问题中的内存泄漏不是由于代码生成,而是因为在同一appdomain中加载了无法释放的程序集(因此不是真正的泄漏)。如果使用“DynamicMethod”,则动态生成的代码与可被垃圾收集的对象相关联。很酷的问题。在今天结束之前,我将举一个例子,说明如何使用另一个AppDomain完成此操作(我正在吃午饭,然后返回工作…)。您计划如何处理生成的程序集?它只是一次执行,还是您要保留它?@LightX我将保留它一段时间,并根据需要从中调用成员,但当新版本的源代码可用时,我将希望转储它并基于新代码创建新程序集。如果没有AppDomain修复程序,这个重复创建程序集的周期(即使我停止引用旧版本)会导致内存使用量增加。此函数调用没有“泄漏”。就让它被垃圾收集吧。要做到这一点,您必须卸载yiu已将生成的程序集加载到的整个域。关于跨越域边界,请注意。如果不从MarshallByRefObject派生封送类型,则封送将使用值复制语义。这可能导致执行速度非常慢,因为跨域边界的通信将非常繁忙。如果你不能让你的类型来自Marsh