Хостинг CLR и домены приложений
По-настоящему оценить достоинства .NET Framework помогают хостинг (hosting) и домены приложений (AppDomains). Благодаря хостингу любое приложение может использовать возможности CLR: переписать приложение при помощи управляемого кода, а также настраивать и дополнять приложение на программном уровне.
Домены приложений позволяют решить проблему, при которой загрузка чужих DLL-библиотек могла нарушить безопасность среды.
Хостинг CLR
.NET Framework работает поверх Windows, а значит, файлы управляемых модулей и сборок должны иметь формат PE, являться исполняемыми файлами (EXE) или динамически подключаемыми библиотеками (DLL). В Microsoft разработали CLR в виде COM-сервера, содержащегося в DLL. При установке .NET Framework COM-сервер, представляющий CLR, регистрируется в реестре. Любое приложение может стать хостом (управляющим приложением) для CLR. Для создания экземпляра CLR стоит использовать специальную функцию, определённую в библиотеке MSCorEE.dll. Эту библиотеку называют оболочкой совместимости (shim) - она не содержит COM-сервер, но определяет, какую версию CLR необходимо создать. На одной машине может быть несколько версий CLR, но только одна версия оболочки совместимости. Версия MSCorEE.dll совпадает с версией самой последней установленной CLR, так что она знает, как найти предыдущие версии.
Хост-приложение может выполнять следующие операции:
- Устанавливать хост-диспетчеры (host managers), занимающиеся выделением памяти, планированием и синхронизацией потоков, загрузкой сборок и т. п.
- Получать информацию о CLR-диспетчерах, то есть запрещать использовать определённые классы или члены. Хост также может указать подлежащий отладке код, а также методы, вызываемые при определённых событиях (выгрузка домена, исключения и т.д.).
- Инициализировать и запускать CLR.
- Загружать сборку и исполнять её код.
- Останавливать CLR.
Домены приложений
В ходе инициализации COM-сервер CLR создаёт домен приложений, который является логическим контейнером для набора сборок. Первый из созданных доменов называют основным (default AppDomain), он уничтожается только при завершении процесса. Помимо основного могут создаваться также и дополнительные. Домены удобны благодаря ряду свойств:
- Объекты, созданные одним доменом приложения, недоступны коду других доменов. Время жизни объекта ограничивается временем жизни домена. Код другого домена может получить доступ к объекту только при помощи продвижения по ссылке (marshal-by-reference) или по значению (marshal-by-value).
- Домены приложений можно выгружать.
- Домены приложений можно индивидуально защищать. Домену приложений можно назначить набор разрешений.
- Домены приложений можно индивидуально настраивать.
У каждого домена есть своя куча загрузчика, ведущая учёт обращения к типу. Если сборка используется в нескольких доменах, то она загружается в каждый домен отдельно, так как домены разрабатывались для изоляции. Однако, существуют сборки для совместного использования. Они загружаются отдельно и выгружаются при завершении процесса.
Доступ к объектам из других доменов
Код, расположенный в одном домене, способен взаимодействовать с типами и объектами другого домена через тщательно определённые механизмы.
В книге описывается несколько примеров доступа к объектам из другого домена, среди которых
- Междоменное взаимодействие с продвижением по ссылке.
- Междоменное взаимодействие с продвижением по значению.
- Междоменное взаимодействие без продвижения.
Все это расписано подробно с примерами кода.
Выгрузка доменов
Для корректной выгрузки домена CLR выполняет набор операций:
- Приостанавливает все потоки в процессе, которые выполняют управляемый код.
- Проверяет все стеки на наличие потоков, которые выполняют или могут вернуться к коду выгружаемого домена. Генерируется
ThreadAbortException, после которого все потоки завершают свою работу. - После выгрузки из домена всех потоков, CLR проходит по куче и устанавливает флаг, сигнализирующий, что реальные объекты уничтожены. При попытке обратиться к таким объектам возникает
AppDomainUnloadedException. - Инициализируется принудительная сборка мусора. Для необходимых объектов вызываются методы финализации.
- Возобновляется работа всех оставшихся потоков. Вызовы
AppDomain.Unload()выполняются синхронно.
Мониторинг доменов
Хост-приложение умеет отслеживать потребляемые доменом ресурсы. Некоторые хосты на основе этой информации принудительно выгружают домен, если потребление памяти или ресурсов выходит за разумные пределы. При включённом мониторинге можно использовать четыре доступных только для чтения свойства класса AppDomain:
- MonitoringSurvivedProcessMemorySize. Число батов, используемых в данный момент всеми доменами под управлением текущего экземпляра CLR. Значение верно с момента последней очистки мусора.
- MonitoringTotalAllocatedMemorySize. Число байтов, выделенных определённым доменом. Значение верно с момента последней очистки мусора.
- MonitoringSurvivedMemorySize. Количество байтов, которые используются определённым доменом. Значение верно с момента последней очистки мусора.
- MonitoringTotalProcessorTime. Возвращает процессорное время, использованное определённым доменом.
Уведомление о первом управляемом исключении домена
При первом появлении исключения CLR задействует любой из методов обратного вызова FirstChanceException, зарегистрированных в домене. Затем CLR ищет блоки catch. Если исключение обрабатывается, то выполнение возвращается в обычный режим. Если блок не найден, то исключение поднимается наверх и также задействует методы обратного вызова. Если исключение так и не удастся обработать, то CLR завершает процесс.
Использование хостами доменов приложений
Исполняемые приложения
Исполняемые приложения (консольное приложение, NT Service, Windows Forms и WPF) являются саморазмещающимися (self-hosted) и снабжены управляемыми EXE-файлами. Если процесс инициализируется при помощи такого файла, то загружается оболочка совместимости, которая через заголовочную информацию в исполняемом файле понимает, какую версию CLR необходимо загрузить в процесс. Затем заголовочные файлы исследуются снова, чтобы найти точку входа в приложение, после чего приложение начинает работу.
Полнофункциональные интернет-приложения Silverlight
Не актуально.
Microsoft ASP.NET и веб-службы XML
При первом запросе клиентом URL-адреса, обрабатываемого библиотекой ISAPI, ASP.NET загружает CLR. Если данный запрос является первым, для данного веб-приложения создаётся новый домен. Затем ASP.NET заставляет CLR выгрузить в новый домен сборку, предоставляющую нужный тип, после чего создаётся экземпляр этого типа и начинают вызываться его методы для исполнения запроса клиента. При наличии ссылок на другие типы могут загружаться дополнительные сборки. Следующие клиентские запросы обрабатываются быстрее, так как типы уже загружены и код скомпилирован.
В одном процессе могут работать несколько веб-приложений, что повышает производительность системы. Для каждого приложения создаётся свой домен.
ASP.NET обладает замечательной возможностью по изменению кода без остановки веб-сервера. Когда файл меняется, старый домен выгружается, а затем с использованием новых файлов создаётся новый домен. При этом ASP.NET использует особую функцию доменов, называемую теневым копированием (shadow copying).
Microsoft SQL Server
Microsoft SQL Server относится к неуправляемым приложениям, так как большая часть написана на C++. Однако он поддерживает создание хранимых процедур на управляемом коде. При первом получении запроса на выполнение хранимой процедуры на управляемом коде, сервер загружает CLR.
Будущее и мечты
В будущем планируется добавление возможности по выбору языка программирования для создания макросов, что позволит выполнять их в домене.
Нетривиальное управление хостингом
Применение управляемого кода
Для изменения заданного по умолчанию поведения CLR при помощи управляемого кода можно определить собственный класс, производный от System.AppDomainManager, переопределив все необходимые виртуальные методы. После этого класс надо скомпоновать в отдельную сборку и поместить в GAC.
Разработка надёжных хост-приложений
Хост может указать CLR, какие действия следует предпринять при сбое в управляемом коде, например:
- Можно прервать поток, если он выполняется слишком долго или не возвращает управление.
- Можно выгрузить домен, при этом закроются все потоки, а код будет выгружен.
- CLR может отключиться, при этом прекращается выполнение именно управляемого кода.
- CLR может выйти из процесса, при этом сначала закрываются все потоки и выгружаются все домены.
Возвращение потока в хост
На рисунке показана архитектура хост-приложения, пытающегося решить проблему вышедшего из-под контроля потока:
- Клиент направляет запрос на сервер.
- Поток сервера принимает запрос и пересылает его потоку из пула для выполнения работы.
- Поток из пула принимает клиентский запрос и выполняет доверенный код.
- Доверенный код входит в блок
tryи вызывает из него другой домен с продвижением по ссылке. Этот домен содержит код сторонних разработчиков. - Хост фиксирует время получения исходного клиентского запроса. Если сторонний код не отвечает за определённое время, хост требует от CLR завершить поток.
- Поток пула начинает завершение и выполняет код очистке. Поток возвращается в домен. Так как программа-заглушка вызвала сторонний код из блока
try, в ней имеется и блокcatch, который перехватывает исключениеThreadAbortException. - В ответ на перехват исключения хост вызывает метод
Thread.ResetAbort(). Данный метод выполняется асинхронно. Он отмечает целевой поток специальным флагом и немедленно возвращает управление. Обнаружив завершение потока, среда пытается перенести его в безопасное место (safe place). Как только потом оказывается в безопасном месте, среда заставляет его выдать исключениеThreadAbortException, после чего поток завершается. - Хост отправляет информацию о сбое клиенту и возвращает поток в пул.
