Домен приложения — это своеобразный контейнер, внутри которого запускается программа, и который изолирует ее во время выполнения. Домен задает границы управляемой памяти для приложения и является контейнером для загруженных сборок и параметров конфигурации приложения. Каждый процесс обычно размещает только один домен приложения — стандартный домен, автоматически созданный CLR при запуске процесса. Однако в рамках одного процесса возможно создавать дополнительные домены приложений.
Несколько доменов приложений обычно применяются с целью обеспечения изоляции схожей с процессами, но с меньшими накладными расходами. Когда дополнительные домены приложения созданы внутри одного и того же процесса, среда CLR обеспечивает для каждого из них уровень изоляции, сходный с таковым в случае выполнения в отдельных процессах. Каждый домен имеет отдельную память, объекты в одном домене не могут конфликтовать с объектами в другом домене, статические члены одного и того же класса в каждом домене имеют независимые значения. Например в ASP.NET для каждого сайта создается отдельный домен приложения, позволяя сайтам выполняться в одном процессе не влияя друг на друга.
Еще одна причина создания отдельного домена приложения — позволить сборкам выгружаться не завершая процесс. После загрузки сборки файл сборки блокируется и его нельзя редактировать или заменить. Чтобы разблокировать файл, сборку надо выгрузить. Единственный способ выгрузить сборку — закрыть домен приложения, в котором она загружена. Это становится проблематично если сборка была загружена в стандартный домен приложения, так как закрытие этого домена означает закрытие приложения. Загрузка сборки в отдельный домен приложения, который может быть уничтожен, позволяет обойти данную проблему.
Создание и уничтожение доменов приложений
Дополнительные домены приложений в процессе создаются и уничтожаются с помощью статических методов AppDomain.CreateDomain
и AppDomain.Unload
:
1 2 3 4 5 6 | static void Main() { AppDomain newDomain = AppDomain.CreateDomain ("New Domain"); newDomain.ExecuteAssembly ("test.exe"); AppDomain.Unload (newDomain); } |
При выгрузке стандартного домена приложения (созданного CLR при запуске приложения) все другие домены приложений выгружаются автоматически и приложение закрывается. Выяснить является ли домен стандартным можно с помощью свойства IsDefaultDomain
объекта AppDomain
.
Класс AppDomainSetup
позволяет задать опции для нового домена. Его основные свойства:
1 2 3 4 5 6 7 | public string ApplicationName { get; set; } // "Дружественное" имя public string ApplicationBase { get; set; } // Базовая папка public string ConfigurationFile { get; set; } public string LicenseFile { get; set; } // Для оказания помощи в автоматическом разрешении сборок: public string PrivateBinPath { get; set; } public string PrivateBinPathProbe { get; set; } |
Свойство ApplicationBase
задает базовый каталог домена приложения, используемый в качестве корня для автоматического поиска сборок. Свойство PrivateBinPath
— это разделенный точками с запятой список подкаталогов ниже базового каталога, в которых CLR должна искать сборки. Задать эти свойства можно только перед запуском домена приложения. Свойство PrivateBinPath
всегда является относительным и находится ниже базовой папки домена приложения. Задавать абсолютные пути нельзя.
1 2 3 4 5 | AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = @"c:\MyBaseFolder"; setup.PrivateBinPath = @"bin\v1.23;bin\plugins"; AppDomain newDomain = AppDomain.CreateDomain ("New Domain", null, setup); newDomain.ExecuteAssembly (@"c:\MyBaseFolder\Startup.exe"); |
Если свойству PrivateBinPathProbe
задать значение отличное от пустой строки, то базовый каталог из пути поиска сборок будет исключен.
Новый домен можно подписать на получение событий разрешения сборок, определенных в домене инициаторе:
1 2 3 4 5 6 7 8 9 10 | static void Main() { AppDomain newDomain = AppDomain.CreateDomain ("test"); newDomain.AssemblyResolve += new ResolveEventHandler (FindAssem); ... } static Assembly FindAssem (object sender, ResolveEventArgs args) { ... } |
Обработчиком событий может быть только статический метод, который будет доступен в обоих доменах. Запускаться обработчик будет в новом домене несмотря на то, что определен в основном.
Непосредственно перед выгрузкой домена приложения, отличного от стандартного, генерируется событие DomainUnload
. Событие можно использовать для выполнения очистки. Домен приложения будет выгружен только после завершения выполнения всех обработчиков.
Непосредственно перед закрытием самого приложения генерируется событие ProcessExit
на всех загруженных доменах, включая стандартный. Выполнения обработчиков этого события нормируется по времени: две секунды на домен и три секунды в общем.
Метод DoCallBack
Класс AppDomain содержит метод DoCallBack
, позволяющий выполнить статический метод в другом домене. При этом сборка, содержащая выполняемый метод, автоматически загрузится в новый домен. В следующем примере метод текущего выполняемого класса запускается в новом домене:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Program { static void Main() { AppDomain newDomain = AppDomain.CreateDomain ("New Domain"); newDomain.DoCallBack (new CrossAppDomainDelegate (SayHello)); AppDomain.Unload (newDomain); } static void SayHello() { Console.WriteLine ("Hi from " + AppDomain.CurrentDomain.FriendlyName); } } |
Обмен данными между доменами
Домены приложений могут использовать именованные ячейки для обмена данными:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Program { static void Main() { AppDomain newDomain = AppDomain.CreateDomain ("New Domain"); // Записать в ячейку "Message" newDomain.SetData ("Message", "guess what..."); newDomain.DoCallBack (SayMessage); AppDomain.Unload (newDomain); } static void SayMessage() { // Читать из ячейки "Message" Console.WriteLine (AppDomain.CurrentDomain.GetData ("Message")); } } |
С помощью метода CreateInstanceAndUnwrap
класса AppDomain
можно создавать экземпляры типов в другом домене. При этом тип, экземпляр которого создается в другом домене должен быть унаследован от класса MarshalByRefObject
.
Метод CreateInstanceAndUnwrap
возвращает прозрачный прокси, который фактически не является прямой ссылкой на объект в другом домене, но ведет себя так, как если бы он ею являлся.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Program { static void Main() { AppDomain newDomain = AppDomain.CreateDomain ("New Domain"); Foo foo = (Foo) newDomain.CreateInstanceAndUnwrap ( typeof (Foo).Assembly.FullName, typeof (Foo).FullName); Console.WriteLine (foo.SayHello()); AppDomain.Unload (newDomain); Console.ReadLine(); } } public class Foo : MarshalByRefObject { public string SayHello() { return "Hello from " + AppDomain.CurrentDomain.FriendlyName; } public override object InitializeLifetimeService() { // Это обеспечивает существование объекта столько, сколько желает клиент return null; } } |
Метод CreateInstanceAndUnwrap
принимает имена сборки и типа в виде строки.
Метод CreateInstanceFromAndUnwrap
аналогичен методу CreateInstanceAndUnwrap
, но вместо имени сборки принимает имя файла сборки или полный путь к нему.