Подтвердить что ты не робот

Как я могу предотвратить утечку памяти CompileAssemblyFromSource?

У меня есть код С#, который использует CSharpCodeProvider.CompileAssemblyFromSource для создания сборки в памяти. После сборки сборки мусора мое приложение использует больше памяти, чем прежде, чем создавать сборку. Мой код находится в веб-приложении ASP.NET, но я дублировал эту проблему в 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: Этот вопрос может быть связан: Динамическая загрузка и выгрузка dll, сгенерированной с помощью CSharpCodeProvider

Обновление 2: Попытка понять доменные приложения больше, я нашел это: Что такое домен приложения - объяснение. Чистые начинающие

Обновление 3: Чтобы уточнить, я ищу решение, которое обеспечивает те же функции, что и код выше (компиляция и обеспечение доступа к сгенерированному коду) без утечки памяти. Похоже, что решение будет включать создание нового AppDomain и маршалинга.

4b9b3361

Ответ 1

Разгрузка сборки не поддерживается. Некоторая информация о том, почему можно найти здесь. Некоторую информацию об использовании AppDomain можно найти здесь.

Ответ 2

Я думаю, у меня есть рабочее решение. Спасибо всем, кто указал мне в правильном направлении (надеюсь).

Ассембли нельзя выгружать напрямую, но 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. Они важны для обеспечения того, чтобы вспомогательная библиотека действительно жила в новом домене.

Если кто-нибудь замечает какие-либо проблемы или имеет предложения по улучшению этого, я бы хотел их услышать.

Ответ 3

Вы также можете найти эту запись в блоге полезной: Использование AppDomain для загрузки и выгрузки динамических сборок. В нем приведен пример кода, демонстрирующего, как создать AppDomain, загрузите в него (динамическую) сборку, выполните некоторую работу в новом AppDomain, затем выгрузите ее.

Изменить: фиксированная ссылка, как указано в комментариях ниже.

Ответ 4

Вы можете подождать до .NET 4.0? С его помощью вы можете использовать деревья выражений и DLR для динамического генерации кода без проблемы с потерей памяти кода-гене.

Другой вариант - использовать .NET 3.5 с динамическим языком, например, IronPython.

EDIT: Пример дерева выражений

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