32

CSharpCodeProvider.CompileAssemblyFromSourceを使用してメモリ内にアセンブリを作成しているC#コードがあります。アセンブリがガベージコレクションされた後、アプリケーションはアセンブリを作成する前よりも多くのメモリを使用します。私のコードはASP.NETWebアプリにありますが、この問題をWinFormに複製しました。System.GC.GetTotalMemory(true)とRed Gate ANTS Memory Profilerを使用して、増加を測定しています(サンプルコードで約600バイト)。

私が行った検索から、リークは新しいタイプの作成に起因しているように見えますが、実際には、参照しているオブジェクトからではありません。私が見つけたいくつかのWebページは、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を使用して生成されたaadllを動的にロードおよびアンロードします

更新2:アプリケーションドメインをもっと理解しようとすると、私はこれを見つけました:アプリケーションドメインとは何ですか-.Net初心者のための説明

更新3:明確にするために、メモリリークを発生させることなく、上記のコードと同じ機能(生成されたコードのコンパイルとアクセスの提供)を提供するソリューションを探しています。このソリューションには、新しいAppDomainの作成とマーシャリングが含まれるようです。

4

4 に答える 4

34

私には実用的な解決策があると思います。私を正しい方向に向けてくれたみんなに感謝します(私は願っています)。

アセンブリを直接アンロードすることはできませんが、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に存在することを確認するためにあります。コンパイルはいくつかのソースコードを受け取り、アセンブリを作成しようとします。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の使用に気付くでしょう。これらは、ヘルパーライブラリが実際に新しいドメインに存在することを保証するために重要です。

誰かが問題に気づいたり、これを改善するための提案があれば、私はそれらを聞いてみたいです。

于 2009-12-01T22:33:55.360 に答える
13

アセンブリのアンロードはサポートされていません。理由に関するいくつかの情報はここにあります。AppDomainの使用に関するいくつかの情報は、ここにあります。

于 2009-11-25T20:16:26.447 に答える
8

また、このブログエントリが役立つ場合があります:AppDomainを使用した動的アセンブリのロードとアンロード。AppDomainを作成し、(動的な)アセンブリをロードし、新しいAppDomainで作業を行ってから、アンロードする方法を示すサンプルコードを提供します。

編集:以下のコメントで指摘されているようにリンクを修正しました。

于 2009-11-25T20:36:17.937 に答える
2

.NET 4.0まで待つことはできますか?これを使用すると、式ツリーとDLRを使用して、コード生成メモリの損失の問題なしにコードを動的に生成できます。

もう1つのオプションは、IronPythonなどの動的言語で.NET3.5を使用することです。

編集:式ツリーの例

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

于 2009-12-03T07:54:30.947 に答える