Домены приложений
Примечание.
Эта статья относится к .NET Framework. Он не применяется к более новым реализациям .NET, включая .NET 6 и более поздние версии.
Операционные системы и среды выполнения обычно содержат определенные средства изоляции приложений друг от друга. Например, в ОС Windows для изоляции приложений используются процессы. Эта изоляция необходима, чтобы гарантировать, что код, выполняемый одним приложением, не сможет нарушить работу других, не связанных с ним приложений.
Домены приложений образуют изолированную область для безопасности, надежности, управления версиями и выгрузки сборок. Домены приложений обычно создаются базовыми средами выполнения, которые отвечают за начальную загрузку среды CLR перед запуском приложения.
Преимущества изоляции приложений
Исторически сложилось так, что для разделения приложений, выполняющихся на одном компьютере, используются границы процессов. Каждое приложение загружается в отдельный процесс, который отделяет его от других приложений, выполняющихся на том же компьютере.
Приложения оказываются изолированными друг от друга, поскольку адреса в памяти привязаны к процессам; бессмысленно использовать в процессе указатель, передаваемый ему из другого процесса. Кроме того, прямые вызовы между процессами невозможны. Вместо этого необходимо использовать прокси, которые обеспечивают уровень косвенности.
Перед запуском управляемый код должен пройти процесс проверки (если администратор не даст разрешение пропустить проверку). В процессе проверки определяется, может ли код предпринимать попытки обращения к неверным адресам памяти или осуществлять другие действия, способные привести к нарушению правильной работы процесса, в котором выполняется этот код. Код, прошедший проверку, называется типобезопасным. Возможность проверки кода на безопасность типа позволяет среде CLR обеспечивать такой же высокий уровень изоляции процессов друг от друга, как и при использовании границ процессов, но со значительно более низкими затратами по производительности.
Домены приложений предоставляют среде CLR более безопасные и гибкие блоки, которые могут использоваться для разделения отдельных приложений. В одном процессе можно запустить несколько доменов приложений с таким же уровнем изоляции, какой обеспечивают отдельные процессы, но без дополнительных издержек на межпроцессные вызовы или переключение между процессами. Возможность выполнения нескольких приложений в одном процессе значительно повышает масштабируемость серверов.
Изоляция приложений также играет важную роль в обеспечении безопасности. Так, например, можно запустить элементы управления нескольких веб-приложений в одном процессе браузера так, что эти элементы управления не смогут получить доступ к данным и ресурсам друг друга.
Изоляция приложений при помощи доменов приложений имеет следующие преимущества.
Сбои в одном из приложений не затронут прочие приложения. Поскольку типобезопасный код не может вызывать сбои в памяти, использование доменов приложений гарантирует, что код, выполняющийся в одном домене, не окажет воздействия на другие приложения процесса.
Можно прекратить выполнение отдельных приложений, не останавливая процесс целиком. Использование доменов приложений позволяет выгружать код, используемый отдельным приложением.
Примечание.
Невозможно выгружать отдельные сборки или типы. Выгрузить можно только домен целиком.
Код, используемый одним приложением, не может иметь непосредственного доступа к коду или ресурсам другого приложения. В среде CLR эта изоляция реализована за счет запрета прямых вызовов между объектами в различных доменах приложений. Объекты, передаваемые от домена к домену, копируются или взаимодействуют через прокси. Если объект копируется, вызов этого объекта является локальным. То есть вызывающий и вызываемый объекты находятся в одном домене приложения. Если доступ к объекту осуществляется через прокси, осуществляется удаленный вызов объекта. В этом случае вызывающий и вызываемый объекты находятся в разных доменах приложений. При междоменных вызовах используется та же инфраструктура удаленных вызовов, что и при вызовах между двумя разными процессами или двумя разными компьютерами. Для правильной JIT-компиляции вызова метода метаданные используемого объекта должны быть доступны для обоих доменов приложений. Если вызывающий домен не имеет доступа к метаданным для вызванного объекта, компиляция может завершиться ошибкой с исключением типа FileNotFoundException. Для получения дополнительной информации см. Remote Objects. Механизм определения способов междоменного доступа для объекта зависит от объекта. Дополнительные сведения см. в разделе System.MarshalByRefObject.
Поведение кода определяется границами приложения, в котором он выполняется. Другими словами, домен приложения предоставляет параметры конфигурации, такие как политики управления версиями приложения, местоположение удаленных сборок, к которым выполняется обращение, и сведения о том, где следует искать локальные сборки, загруженные в домен.
Разрешения, предоставленные коду, могут управляться доменом приложения, в котором выполняется этот код.
Домены приложений и сборки
В этом разделе описывается связью между доменами приложений и сборками. Чтобы выполнить код сборки, ее необходимо загрузить в домен приложения. Как правило, для запуска приложения в домен приложения необходимо загрузить несколько сборок.
Способ загрузки определяет, можно ли использовать JIT-скомпилированный код сборки в нескольких доменах приложений в процессе и можно ли выгружать сборку из процесса.
Если сборка загружается как нейтральная к домену, то все домены приложений с одним набором разрешений безопасности могут совместно использовать JIT-скомпилированный код, что снижает требуемый для приложения объем памяти. Однако сборку нельзя выгружать из процесса.
Если сборка не загружается как нейтральная к домену, она должна быть JIT-скомпилирована в каждом домене приложения, в который она загружается. Однако сборку можно выгрузить из процесса, выгрузив все домены приложений, в которые она загружается.
Следует ли загружать сборки как нейтральные к домену, определяется в хост-приложении среды выполнения при загрузке среды выполнения в процесс. Для управляемых приложений примените атрибут LoaderOptimizationAttribute к методу точки входа процесса и укажите значение из связанного перечисления LoaderOptimization. Для неуправляемых приложений, в которых размещается среда CLR, при вызове функции CorBindToRuntimeEx задайте соответствующий флаг.
Существует три варианта загрузки нейтральных к домену сборок.
LoaderOptimization.SingleDomain не загружает сборки как нейтральные к домену, за исключением Mscorlib, которая всегда загружается как нейтральная к домену. Этот вариант называется однодоменным, поскольку обычно он используется, когда в хост-приложении запускается только одно приложение в процессе.
LoaderOptimization.MultiDomain загружает все сборки как нейтральные к домену. Этот вариант используется при наличии нескольких доменов приложений, в каждом из которых выполняется одинаковый код.
LoaderOptimization.MultiDomainHost загружает сборки со строгими именами как нейтральные к домену, если они и все их зависимости установлены в глобальном кэше сборок. Другие сборки загружаются и JIT-компилируются отдельно для каждого домена приложения, в котором они загружаются, поэтому могут выгружаться из процесса. Этот вариант используется при работе нескольких приложений в одном процессе или при наличии набора сборок, которые являются общими для нескольких доменов приложений, и сборок, которые должны выгружаться из процесса.
JIT-скомпилированный код нельзя сделать общим для сборок, загруженных в контекст загрузки с помощью метода LoadFrom класса Assembly или загруженных из образов с помощью перегрузок метода Load, задающего байтовые массивы.
Сборки, которые были скомпилированы в машинный код с помощью Ngen.exe (средства для создания машинных образов), могут совместно использоваться доменами приложений, если они загружены как нейтральные к домену при первой загрузке в процесс.
JIT-скомпилированный код для сборки, содержащей точку входа приложения, может быть общим, только если все его зависимости могут использоваться совместно.
Сборка, нейтральная к домену, может JIT-компилироваться более одного раза. Например, когда наборы разрешений безопасности двух доменов приложений отличаются, они не могут совместно использовать один и тот же JIT-скомпилированный код. Однако каждая копия JIT-скомпилированной сборки может использоваться совместно с другими доменами приложений, имеющими такой же набор разрешений.
При принятии решения о том, следует ли загружать сборки как нейтральные, осуществляется компромисс между сокращением расхода памяти и производительностью.
Доступ к статическим данным и методам для нейтральных к домену сборок осуществляется медленнее из-за необходимости изоляции сборок. Каждый домен приложения, обращающийся к сборке, должен обладать собственной копией статических данных, чтобы избежать ссылок на объекты в статических полях, пересекающих границы домена. В результате среда выполнения содержит дополнительные правила для перенаправления вызывающего объекта к соответствующей копии статических данных или метода. Эти дополнительные правила замедляют вызов.
При загрузке сборки как нейтральной к домену необходимо найти и загрузить все зависимости сборки, поскольку зависимость, которую нельзя загрузить как нейтральную, не позволяет загрузить сборку как нейтральную.
Домены приложений и потоки
Домен приложения формирует изолированную область для безопасности, управления версиями, надежности и выгрузки управляемого кода. Поток представляет собой конструкцию операционной системы, используемую в среде CLR для выполнения кода. Во время выполнения весь управляемый код загружается в домен приложения и выполняется в одном или нескольких управляемых потоках.
Однозначного соответствия между потоками и доменами приложений не существует. В одном домене приложения могут одновременно выполняться несколько потоков, при этом конкретный поток не ограничен одним доменом приложения. Таким образом, потоки могут использоваться в разных доменах приложений. Новый поток для каждого домена приложения не создается.
В любой момент времени каждый поток выполняется в каком-либо домене приложения. В домене приложения может выполняться один или несколько потоков или не выполняться ни одного потока. Среда выполнения отслеживает соответствие потоков и доменов приложений, в которых они выполняются. В любой момент времени можно найти домен, в котором выполняется поток, вызвав метод Thread.GetDomain.
Домены приложений и язык и региональные параметры
Язык и региональные параметры, представляемые объектом CultureInfo, связаны с потоками. Чтобы получить язык и региональные параметры, связанные с выполняющимся в данный момент потоком, используйте свойство CultureInfo.CurrentCulture, а чтобы получить или задать язык и региональные параметры, связанные с выполняющимся в данный момент потоком, используйте свойство Thread.CurrentCulture. Если связанные с потоком язык и региональные параметры явно заданы с помощью свойства Thread.CurrentCulture, они будут связаны с этим потоком и за пределами домена приложения. В противном случае связанные с потоком язык и региональные параметры в любой момент времени определяются значением свойства CultureInfo.DefaultThreadCurrentCulture в домене приложения, где выполняется поток:
Если значение этого свойства — не
null
, возвращаемые им язык и региональные параметры связаны с потоком (и следовательно возвращаются свойствами Thread.CurrentCulture и CultureInfo.CurrentCulture).Если значение этого свойства –
null
, с потоком связываются текущие настройки языка и региональных параметров системы.
Программирование с использованием доменов приложений
Домены приложений обычно создаются и управляются программно, хост-приложениями среды выполнения. Однако иногда работать с доменами приложений требуется самим приложениям. Например, приложение может загружать компонент в домен, чтобы иметь возможность выгрузить домен (и компонент) без необходимости останавливать все приложение.
AppDomain — это программный интерфейс для доменов приложений. Этот класс содержит методы для создания и выгрузки доменов, для создания экземпляров типов в доменах и для подписки на различные уведомления, такие как выгрузка домена приложения. В следующей таблице перечислены часто используемые методы AppDomain.
Метод AppDomain | Description |
---|---|
CreateDomain | Создает новый домен приложения. Рекомендуется использовать перегрузку этого метода, в которой определяется объект AppDomainSetup. Это предпочтительный способ установки свойств нового домена, таких как база приложения или корневой каталог приложения, расположение файла конфигурации домена и путь поиска, используемый средой CLR для загрузки сборок в домен. |
ExecuteAssembly и ExecuteAssemblyByName. | Выполняет сборку в домене приложения. Это метод экземпляра, поэтому его можно использовать для выполнения кода в другом домене приложения, на который имеется ссылка. |
CreateInstanceAndUnwrap | Создает экземпляр указанного типа в домене приложения и возвращает прокси. Этот метод служит для избежания загрузки сборки, содержащей созданный тип, в вызывающую сборку. |
Unload | Корректно завершает работу домена. Домен приложения не выгружается до тех пор, пока все запущенные в домене потоки не будут остановлены или исключены из домена. |
Примечание.
Среда CLR не поддерживает сериализацию глобальных методов, поэтому для выполнения глобальных методов в других доменах приложений использовать делегаты нельзя.
Неуправляемые интерфейсы, описанные в спецификации Hosting Interfaces Specification для среды CLR, также предоставляют доступ к доменам приложений. Хост-приложения среды выполнения могут использовать интерфейсы из неуправляемого кода для создания доменов приложений внутри процесса и для получения доступа к ним.
Переменная среды COMPLUS_LoaderOptimization
Переменная среды, которая задает политику оптимизации загрузчика по умолчанию для исполняемого приложения.
Синтаксис
COMPLUS_LoaderOptimization = 1
Замечания
Стандартное приложение загружает в домен приложения несколько сборок. После этого можно выполнить содержащийся в них код.
Способ загрузки определяет, может ли JIT-скомпилированный код сборки использоваться несколькими доменами приложения в одном процессе.
Если сборка загружается как независимая от домена, то все домены приложения с одним набором разрешений безопасности могут использовать один JIT-скомпилированный код. В этом случае приложение использует меньше памяти.
Если сборка загружается как зависимая от домена, ее необходимо JIT-компилировать во всех соответствующих доменах приложения, при этом в каждом случае загрузчик должен использовать разные внутренние ресурсы.
Если значение флага среды COMPLUS_LoaderOptimization — 1, то хост-приложение среды выполнения использует способ загрузки SingleDomain. Он принудительно загружает все сборки как зависимые от домена, за исключением сборки Mscorlib, которая всегда загружается как независимая. Этот вариант называется однодоменным, поскольку обычно он используется, когда в хост-приложении запускается только одно приложение в процессе.
Внимание
Флаг среды COMPLUS_LoaderOptimization предназначен для сценариев диагностики и тестирования. Когда он включен, работа может существенно замедляться, а использование памяти — увеличиваться.
Пример кода
Чтобы все сборки для службы IISADMIN загружались как зависимые от домена, добавьте код COMPLUS_LoaderOptimization=1
в многострочный параметр среды в разделе HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\IISADMIN.
Key = HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\IISADMIN
Name = Environment
Type = REG_MULTI_SZ
Value (to append) = COMPLUS_LoaderOptimization=1