Передать и выполнить делегат в отдельном домене приложений - PullRequest
31 голосов
/ 05 января 2010

Я хочу превысить некоторый кусок кода в отдельном домене приложений с делегатом. Как я могу это сделать?

UPD1 : некоторые подробности о моей проблеме Моя программа обрабатывает некоторые данные (одна итерация: получать данные из БД, оценивать их и создавать сборки во время выполнения, выполнять динамические сборки и записывать результаты в БД).

Текущее решение: каждая итерация выполняется в отдельном потоке. Лучшее решение: каждая итерация выполняется в отдельном домене приложений (для выгрузки динамических сборок).

UPD2 : Всем спасибо за ответы.

Я нашел один для меня в этой теме: Замена Process.Start на домены приложений

Ответы [ 4 ]

48 голосов
/ 06 января 2010

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

Чтобы это работало, ваш объект должен наследоваться от MarshalByRefObject .

Вот пример:

    public interface IRuntime
    {
        bool Run(RuntimesetupInfo setupInfo);
    }

    // The runtime class derives from MarshalByRefObject, so that a proxy can be returned
    // across an AppDomain boundary.
    public class Runtime : MarshalByRefObject, IRuntime
    {
        public bool Run(RuntimeSetupInfo setupInfo)
        {
            // your code here
        }
    }

    // Sample code follows here to create the appdomain, set startup params
    // for the appdomain, create an object in it, and execute a method
    try
    {
        // Construct and initialize settings for a second AppDomain.
        AppDomainSetup domainSetup = new AppDomainSetup()
        {
            ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
            ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
            ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
            LoaderOptimization = LoaderOptimization.MultiDomainHost
        };

        // Create the child AppDomain used for the service tool at runtime.
        childDomain = AppDomain.CreateDomain(
            "Your Child AppDomain", null, domainSetup);

        // Create an instance of the runtime in the second AppDomain. 
        // A proxy to the object is returned.
        IRuntime runtime = (IRuntime)childDomain.CreateInstanceAndUnwrap(
            typeof(Runtime).Assembly.FullName, typeof(Runtime).FullName);

        // start the runtime.  call will marshal into the child runtime appdomain
        return runtime.Run(setupInfo);
    }
    finally
    {
        // runtime has exited, finish off by unloading the runtime appdomain
        if(childDomain != null) AppDomain.Unload(childDomain);
    }

В приведенном выше примере он закодирован для выполнения метода «Run», передавая некоторую информацию о настройке, и завершение метода Run определяется, чтобы указать, что весь код в дочернем домене AppDomain завершил работу, поэтому у нас есть блок, обеспечивающий выгрузку домена приложений.

Вы часто можете быть осторожны с тем, какие типы вы помещаете в какие сборки - вы можете использовать интерфейс и помещать его в отдельную сборку, которая будет как вызывающей (наш код, который устанавливает домен приложения, так и вызывает его). ) и реализатор (класс Runtime) зависят от. Этот IIRC позволяет родительскому домену AppDomain загружать только сборку, содержащую интерфейс, в то время как дочерний домен приложения будет загружать как сборку, содержащую среду выполнения, так и ее зависимость (сборку IRuntime). Любые определяемые пользователем типы, которые используются интерфейсом IRuntime (например, наш класс RuntimeSetupInfo), обычно также должны быть помещены в ту же сборку, что и IRuntime. Кроме того, будьте осторожны с тем, как вы определяете эти пользовательские типы - если они являются объектами передачи данных (как, вероятно, RuntimeSetupInfo), вам, вероятно, следует пометить их атрибутом [serializable], чтобы копия объекта передавалась (сериализовалась родительское приложение для ребенка). Вы хотите избежать перенаправления вызовов из одного домена приложения в другой, так как это довольно медленно. Передача значений DTO по значению (сериализация) означает, что доступ к значениям в DTO не требует межквартирного вызова (поскольку дочерний домен приложения имеет собственную копию оригинала). Конечно, это также означает, что изменения значений не отражаются в исходном DTO родительского домена приложения.

Как закодировано в примере, родительский домен приложения фактически завершит загрузку сборок IRuntime и Runtime, но это потому, что при вызове CreateInstanceAndUnwrap я использую typeof (Runtime), чтобы получить имя сборки и полное имя типа , Вместо этого вы можете жестко закодировать или извлечь эти строки из файла, что бы отделить зависимость.

Существует также метод в AppDomain с именем DoCallBack, который выглядит так, как будто он позволяет вызывать делегата в чужом AppDomain. Однако тип делегата, который он принимает, имеет тип 'CrossAppDomainDelegate'. Определение которого:

public delegate void CrossAppDomainDelegate()

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

Кроме того, я бы рекомендовал просмотреть свойство LoaderOptimization . То, что вы установили, может оказать существенное влияние на производительность, поскольку некоторые настройки этого свойства заставляют новый домен приложения загружать отдельные копии всех сборок (и их JIT и т. Д.), Даже если (IIRC) сборка находится в GAC ( то есть это включает сборки CLR). Это может дать вам ужасную производительность, если вы используете большое количество сборок из дочернего домена приложения. Например, я использовал WPF из дочерних доменов приложений, что вызывало огромные задержки при запуске моего приложения, пока я не установил более подходящую политику загрузки.

8 голосов
/ 14 января 2013

Для выполнения делегата в другом домене приложения вы можете использовать System.AppDomain.DoCallBack () . Связанная страница MSDN имеет отличный пример. Обратите внимание, что вы можете использовать только делегаты типа CrossAppDomainDelegate .

6 голосов
/ 05 января 2010

Вам нужно прочитать о .NET Remoting и, в частности, о Удаленные объекты , так как это все, что вы можете пройти через домены приложений.Дело в том, что ваш объект передается по значению или по ссылке (через прокси).

По значению требует, чтобы ваш объект был сериализуемым.Делегаты не сериализуются на афаике.Это означает, что это неправильный маршрут для следования.

По ссылке требуется, чтобы вы унаследовали от MarshalByRefObject .Таким образом, инфраструктура удаленного взаимодействия может создать прокси.Однако это также означает, что ваш делегат будет выполняться на машине, которая его создает, а не в домене клиентских приложений.

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

3 голосов
/ 05 января 2010

Это не отвечает на ваш вопрос напрямую, но, возможно, было бы лучше создать службу WCF или веб-службу в другом домене приложений, чтобы сохранить изоляцию. Я не знаю вашей конкретной ситуации, но изолированное архитектурное проектирование - почти всегда верный путь.

...