Как заставить объект освободить ссылки, когда он создается новым AppDomain и ConstructorInfo.Invoke? - PullRequest
1 голос
/ 07 марта 2012

Вот еще один для освобождения объектов, созданных отражением:

Мы работаем с инструментом отчетности (Active Reports 6), который создает DLL для каждого отчета.

У нас много клиентов, которые используют похожие, но уникальные отчеты.

Отчеты читаются через веб-интерфейс. Мы запускаем несколько сайтов, по одному для каждого клиента.

Наш выбор: 1) Поместите все отчеты в один большой проект, который будет вызываться всеми сайтами. Стоимость. Его необходимо будет перекомпилировать каждый раз, когда мы вносим небольшие изменения в какой-либо отчет, что может создать проблемы для всех сайтов. 2) Создайте целую кучу похожих маленьких проектов, по одному для каждого сайта - скажем, ради пространства, что это тоже создает проблемы. 3) Создайте «Фабрику отчетов», которая будет использовать отражение для подключения библиотек отчетов при необходимости.

Мы выбрали «3».

Проблема: конечный продукт работает нормально, за исключением одной вещи: он не выпустит отчет dll, когда будет готов. В настоящее время нет проблем с операцией в тестовой среде, но если вы попытаетесь что-либо сделать в папке с dll отчета, вы получите следующее сообщение об ошибке: «Это действие не может быть выполнено, потому что папка или файл в нем открыта другая программа "

После исследования этого сайта и других, мы поняли, что нам нужен AppDomain для каждого вызова, который может быть чисто выгружен. По-прежнему возникли проблемы, мы поняли, что объект AppDomainSetup должен иметь параметр, позволяющий оптимизировать его для нескольких пользователей (LoaderOptimization.MultiDomain). Это не сработало.

К сожалению, базовый объект (отчет Active 6) не может быть сериализован, поэтому мы не можем сделать глубокую копию и обработать исходный объект.

После всего этого у нас все еще возникают проблемы.

Вот код (C #):

private object WireUpReport(ReportArgs args)
{

    //The parameter 'args' is a custom type (ReportArgs) which merely contains a 
name/value pair collection.


    object myReport = null;
    string sPath = String.Empty;
    string sFriendlyName = String.Empty;
    sFriendlyName = System.Guid.NewGuid().ToString();
    Assembly asmReport = null;
    AppDomainSetup ads = null;
    AppDomain adWireUp = null;
    ConstructorInfo ci = null;
    Type myReportType = null;
    Type[] parametypes = null;
    object[] paramarray = null;

    object retObject = null;

    try
    {

        //Get Report Object
        sPath = GetWireUpPath(args); //Gets the path to the required dll; kept in a config file
                                     //This parameter is used in an overloaded constructor further down

        ads = new AppDomainSetup();
        ads.ApplicationBase = Path.GetDirectoryName(sPath);
        ads.LoaderOptimization = LoaderOptimization.MultiDomain;
        adWireUp = AppDomain.CreateDomain(sFriendlyName, AppDomain.CurrentDomain.Evidence, ads);
        asmReport = adWireUp.GetAssemblies()[0];
        asmReport = Assembly.LoadFrom(sPath);

        //Create parameters for wireup
        myReportType = asmReport.GetExportedTypes()[0];
        parametypes = new Type[1];
        parametypes[0] = typeof(ReportArgs);
        ci = myReportType.GetConstructor(parametypes);
        paramarray = new object[1];
        paramarray[0] = args;

        //Instantiate object
        myReport = ci.Invoke(paramarray);

        return myReport;

    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        //Make sure Assembly object is released.
        if (adWireUp != null)
        {
            AppDomain.Unload(adWireUp);
        }
        if (asmReport != null)
        {
            asmReport = null;
        }

        if (ads != null)
        {
            ads = null;
        }
        if (adWireUp != null)
        {
            adWireUp = null;
        }
        if (ci != null)
        {
            ci = null;
        }
        if (myReportType != null)
        {
            myReportType = null;
        }
        if (parametypes != null)
        {
            parametypes = null;
        }
        if (paramarray != null)
        {
            paramarray = null;
        }

    }
}

Объект, который возвращается из этого кода, приводится к типу ActiveReports и затем передается нашему приложению.

Любая помощь будет принята с благодарностью. Спасибо

1 Ответ

2 голосов
/ 07 марта 2012

Ваш код выглядит так, как будто вы серьезно не понимаете, как взаимодействовать с отдельным доменом приложений.

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

Способ, которым вы открываете эту линию связи, заключается в определении прокси-объекта, который можно создать внутри другого домена приложений, а затем пересечь границу обратно к вашему текущему домену приложений. Чтобы пересечь границу, необходимо, чтобы ваш объект был помечен как [Serializable] или унаследован от MarshalByRefObject. Поскольку на самом деле мы хотим поговорить со ссылкой в другом домене приложения , а не просто иметь ее копию, нам нужен прокси-сервер, чтобы сделать последнее.

private class CrossDomainQuery : MarshalByRefObject
{
    public void LoadDataFromAssembly(string assemblyPath)
    {
        var assembly = Assembly.LoadFrom(assemblyPath);
        //TODO: Do something with your assembly
    }
}

В AppDomain существует метод CreateInstanceAndUnwrap (), который создаст экземпляр этого коммуникационного объекта внутри другого AppDomain , а затем вернет вам объект __TransparentProxy, который можно привести к типу прокси.

var crossDomainQuery = (CrossDomainQuery)adWireUp.CreateInstanceAndUnwrap(
    typeof(CrossDomainQuery).Assembly.FullName,
    typeof(CrossDomainQuery).FullName);

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

crossDomainQuery.LoadDataFromAssembly(assemblyPath);

Так как это отличается от того, что делает ваш текущий пример кода?

Ваш текущий код на самом деле не выполняет ничего полезного внутри другого AppDomain.

adWireUp = AppDomain.CreateDomain(sFriendlyName, AppDomain.CurrentDomain.Evidence, ads);
asmReport = adWireUp.GetAssemblies()[0];
asmReport = Assembly.LoadFrom(sPath);

Это создает новый AppDomain, но затем он загружает все сборки из этого AppDomain в ваш текущий AppDomain. Кроме того, он явно загружает вашу сборку отчетов в ваш текущий AppDomain.

Создание домена приложения и вызов его методов не означает, что ваш код выполняется внутри него, так же как чтение о другой стране означает, что вы сейчас общаетесь с кем-то внутри него.

Даже если вы создаете прокси-объект и выполняете код внутри этого другого домена приложений, есть несколько вещей, о которых следует знать.

1) Оба AppDomains должны иметь возможность видеть тип, используемый для прокси-сервера, и вам может потребоваться обработать события AssemblyResolve для любого AppDomain вручную (хотя бы временно), чтобы помочь разрешить это.

2) Создание доменов приложений довольно дорого. Как правило, они не используются в ситуациях, когда вам нужно действительно быстро что-то раскрутить, предпринять какие-то действия и исчезнуть. Вы должны планировать либо держать их как можно дольше, либо быть готовыми к снижению производительности при каждом вызове.

3) Вы сказали, что тип создаваемого вами отчета не сериализуем, и возможность сериализации объекта является требованием для передачи этого типа обратно из другого домена приложения. Можно определить сериализуемый класс, который может передавать соответствующие данные через границу, и использовать его для передачи данных отчета, но вам придется определить, подходит ли это для вашей конкретной ситуации.

Кроме того, кроме случаев, когда у вас нет логики, которая зависит от переменных, для которых задано значение null, установка значения null в вашем файле finally ничего не даст и не усложнит ваш код.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...