如何防止 CompileAssemblyFromSource 泄漏内存?

发布于 2024-08-12 15:22:35 字数 1692 浏览 14 评论 0原文

我有一些 C# 代码,它使用 CSharpCodeProvider.CompileAssemblyFromSource 在内存中创建程序集。程序集被垃圾收集后,我的应用程序使用的内存比创建程序集之前更多。我的代码位于 ASP.NET Web 应用程序中,但我在 WinForm 中重复了此问题。我使用 System.GC.GetTotalMemory(true) 和 Red Gate ANTS Memory Profiler 来测量增长(示例代码大约 600 字节)。

从我所做的搜索来看,泄漏似乎来自新类型的创建,而不是真正来自我持有引用的任何对象。我发现的一些网页提到了有关AppDomain的内容,但我不明白。有人可以解释一下这里发生了什么以及如何解决它吗?

以下是一些泄漏的示例代码:

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: 这个问题可能相关:动态加载和卸载使用 CSharpCodeProvider 生成的 dll

更新 2: 试图更多地了解应用程序域,我发现了这个:什么是应用程序域 - 对 .Net 初学者的解释

更新 3:为了澄清,我正在寻找一种解决方案,该解决方案提供与上述代码相同的功能(编译并提供对生成代码的访问)而不泄漏内存。看起来解决方案将涉及创建新的 AppDomain 和封送。

I have some C# code which is using CSharpCodeProvider.CompileAssemblyFromSource to create an assembly in memory. After the assembly has been garbage collected, my application uses more memory than it did before creating the assembly. My code is in a ASP.NET web app, but I've duplicated this problem in a WinForm. I'm using System.GC.GetTotalMemory(true) and Red Gate ANTS Memory Profiler to measure the growth (about 600 bytes with the sample code).

From the searching I've done, it sounds like the leak comes from the creation of new types, not really from any objects that I'm holding references to. Some of the web pages I've found have mentioned something about AppDomain, but I don't understand. Can someone explain what's going on here and how to fix it?

Here's some sample code for leaking:

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;
    }
}

Update 1: This question may be related: Dynamically loading and unloading a a dll generated using CSharpCodeProvider

Update 2: Trying to understand application domains more, I found this: What is an application domain - an explanation for .Net beginners

Update 3: To clarify, I'm looking for a solution that provides the same functionality as the code above (compiling and providing access to generated code) without leaking memory. It looks like the solution will involve creating a new AppDomain and marshaling.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

如何视而不见 2024-08-19 15:22:35

我想我有一个可行的解决方案。感谢大家为我指明了正确的方向(我希望如此)。

程序集不能直接卸载,但 AppDomain 可以。我创建了一个帮助程序库,该库加载到新的 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 中。编译需要一些源代码并尝试创建一个程序集。 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) 的实例,使用它编译新程序集(隐藏),从该新程序集运行一些代码,然后将域卸载到释放内存。

您会注意到 MarshalByRefObject 和 CreateInstanceFromAndUnwrap 的使用。这些对于确保帮助程序库确实存在于新域中非常重要。

如果有人注意到任何问题或有改进的建议,我很乐意听到他们的声音。

I think I have a working solution. Thanks to everyone for pointing me in the right direction (I hope).

Assemblies can't be unloaded directly, but AppDomains can. I created a helper library that gets loaded in a new AppDomain and is able to compile a new assembly from code. Here's what the class in that helper library looks like:

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);
    }

}

It's very basic, but was enough for testing. PrintDomain is there to verify that it does live in my new AppDomain. Compile takes some source code and tries to create an assembly. Run lets us test executing static methods from the given source code.

Here's how I use the helper library:

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);
}

It basically creates the domain, creates an instance of my helper class (CompilerRunner), uses it to compile a new assembly (hidden), runs some code from that new assembly, and then unloads the domain to free up memory.

You'll notice the use of MarshalByRefObject and CreateInstanceFromAndUnwrap. These are important for ensuring that the helper library really does live in the new domain.

If anyone notices any problems or has suggestions for improving this, I'd love to hear them.

薄情伤 2024-08-19 15:22:35

不支持卸载程序集。有关原因的一些信息可以在此处找到。
有关使用 AppDomain 的一些信息可以在此处找到。

Unloading an assembly is not supported. Some information on why can be found here.
Some information on using an AppDomain can be found here.

要走就滚别墨迹 2024-08-19 15:22:35

您可能还会发现此博客条目很有用:使用 AppDomain 加载和卸载动态程序集。 它提供了一些示例代码,演示如何创建 AppDomain、将(动态)程序集加载到其中、在新 AppDomain 中执行一些操作,然后卸载它。

编辑:固定链接,如下面的评论中指出的。

You may also find this blog entry useful: Using AppDomain to Load and Unload Dynamic Assemblies. It provides some example code demonstrating how create an AppDomain, load a (dynamic) assembly into it, do some work in the new AppDomain then unload it.

Edit: fixed link as pointed out in comments below.

薔薇婲 2024-08-19 15:22:35

你能等到.NET 4.0吗?有了它,您可以使用表达式树和 DLR 动态生成代码,而不会出现代码生成内存丢失问题。

另一种选择是将 .NET 3.5 与 IronPython 等动态语言结合使用。

编辑:表达式树示例

http://www.infoq.com/articles/expression-compiler

Can you wait until .NET 4.0? With it you can use expression trees and the DLR to dynamically generate code without the code-gen memory loss issue.

Another option is to use .NET 3.5 with a dynamic language like IronPython.

EDIT: Expression Tree Example

http://www.infoq.com/articles/expression-compiler

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文