Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
428 views
in Technique[技术] by (71.8m points)

c# - How can I prevent CompileAssemblyFromSource from leaking memory?

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;
";
    sourceCode += "public class HelloWord {
";
    sourceCode += "  public HelloWord() {
";
    sourceCode += "    Console.WriteLine("hello world");
";
    sourceCode += "  }
";
    sourceCode += "}
";

    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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...