使用.Net建立的可執行程序*.exe,並沒有直接承載到進程當中,而是承載到應用程序域(AppDomain)當中。應用程序域是.Net引入的一個新概念,它比進程所占用的資源要少,可以被看做是一個輕量級的進程。一個應用程序域可以有多個線程,一個線程也可以穿梭於多個應用程序域。
在一個進程中可以包含多個應用程序域,一個應用程序域可以裝在一個可執行程序(*.exe)或者多個程序集(*.dll)。這樣可以使應用程序域之間實現深度隔離,即使進程中的某個應用程序域出現錯誤,也不會影響其他應用程序域的正常運作。
當一個程序集同時被多個應用程序域調用時,會出現兩種情況:
- CLR分別為不同的應用程序域加載此程序集。
- CLR把此程序集加載到所有的應用程序域之外,並實現程序集共享,此情況比較特殊,被稱作為Domain Neutarl。
應用程序域:(Application Domain,簡稱App Domain)一組程序集的一個邏輯容器,進程中的一個邏輯分區。通常由運行時宿主創建和操作。AppDomain唯一的作用就是進行隔離。
具體功能:
- 隔離,一個AppDomain中的代碼創建的對象不能由另一個AppDomain中的代碼直接訪問。達到隔離應用程序的效果。當然如果要訪問別的AppDomain中的內容,可以使用“按引用封送”或者“按值封送”的語義。
- AppDomain可以卸載,但不能卸載單獨的程序集或類型,只能卸載整個應用程序域。從而卸載包含在該AppDomain中的所有程序集。
- AppDomain可以單獨保護,AppDomain在創建后,會應用一個權限集,它決定了向這個AppDomain中運行的程序集授予的最大權限。從而保護宿主加載的代碼不被破壞。
- 可以單獨實施配置,AppDomain在創建后,會關聯一組配置設置。這些設置主要影響CLR在AppDomain中加載程序集的方式。這些設置涉及搜索路徑、版本重定向、卷影復制以及加載器優化。
- 需要隔離的程序集,譬如一些特別容易引起崩潰的代碼可以考慮單獨運行於一個特定的 appDomain;
- 不同安全級別的程序集,如果需要為自己的代碼划分安全執行的邊界,可以考慮將不同安全級別的代碼單獨創建於某個設定了不同安全信息的 appDomain
- 從性能上考慮,有些程序集可能會消耗大量資源,盡管在托管環境下,基本上不存在資源消耗漏洞,但是總會存在特定時間訪問密集造成消耗大量資源的情況,這時可以考慮創建單獨的 appDomain ,在資源消耗超過臨界點后進行 appDomain 的卸載,適應系統運行要求。 Asp.net 中利用不同的appDomain 來提供支持就是為了防止一個應用程序的崩潰影響其他 asp.net 應用程序,同時 , 在不重新啟動的系統不重新啟動 IIS 不影響 asp.net 自身服務提供的情況下將一個 appDomain 卸掉同時啟動新的 appDomain ,理想情況下可以實現 web 系統的長時間在線(這以往是昂貴的 unix 的特性,終於被 MS “借鑒”了)。
- 不同版本的同一應用程序集的同時運行。這個在 COM 時代是一個大問題,現在通過 appDomain ,實現了在一個進程中執行版本不同的兩個程序集,可以做到良好的兼容性。
- 動態加載一些程序。
一、AppDomain的屬性與方法
在System命名空間下就存在AppDomain類,用於管理應用程序域。
AppDomain的常用屬性:
屬性 | 說明 |
ActivationContext | 獲取當前應用程序域的激活上下文。 |
ApplicationIdentity | 獲得應用程序域中的應用程序標識。 |
BaseDirectory | 獲取基目錄。 |
CurrentDomain | 獲取當前 Thread 的當前應用程序域。 |
Id | 獲得一個整數,該整數唯一標識進程中的應用程序域。 |
RelativeSearchPath | 獲取相對於基目錄的路徑,在此程序集沖突解決程序應探測專用程序集。 |
SetupInformation | 獲取此實例的應用程序域配置信息。 |
AppDomain的常用方法:
方法 | 說明 |
CreateDomain | 創建新的應用程序域。 |
CreateInstance | 創建在指定程序集中定義的指定類型的新實例。 |
CreateInstanceFrom | 創建在指定程序集文件中定義的指定類型的新實例。 |
DoCallBack | 在另一個應用程序域中執行代碼,該應用程序域由指定的委托標識。 |
ExecuteAssembly | 執行指定文件中包含的程序集。 |
ExecuteAssemblyByName | 執行程序集。 |
GetAssemblies | 獲取已加載到此應用程序域的執行上下文中的程序集。 |
GetCurrentThreadId | 獲取當前線程標識符。 |
GetData | 為指定名稱獲取存儲在當前應用程序域中的值。 |
IsDefaultAppDomain | 返回一個值,指示應用程序域是否是進程的默認應用程序域。 |
SetData | 為應用程序域屬性分配值。 |
Load | 將 Assembly 加載到此應用程序域中。 |
Unload | 卸載指定的應用程序域。 |
AppDomain事件:
事件 | 說明 |
AssemblyLoad | 在加載程序集時發生。 |
AssemblyResolve | 在對程序集的解析失敗時發生。 |
DomainUnload | 在即將卸載 AppDomain 時發生。 |
ProcessExit | 當默認應用程序域的父進程存在時發生。 |
ReflectionOnlyAssemblyResolve | 當程序集的解析在只反射上下文中失敗時發生。 |
ResourceResolve | 當資源解析因資源不是程序集中的有效鏈接資源或嵌入資源而失敗時發生。 |
TypeResolve | 在對類型的解析失敗時發生。 |
UnhandledException | 當某個異常未被捕獲時出現。 |
二、在AppDomain中加載程序集
通過CreateDomain方法可以建立一個新的應用程序域。
下面給出一個使用CreateDomain建立一個應用程序域,並使用Load方法加載程序集Model.dll。
最后使用GetAssemblies方法,列舉此應用程序域中的所有程序集。
public class Program { static void Main(string[] args) { var domain = AppDomain.CreateDomain("MyAppDomain"); domain.Load(@"控制台 - 學習測試"); foreach (var assembly in domain.GetAssemblies()) { Console.WriteLine(assembly.FullName); } Console.ReadKey(); } }
輸出結果如下:
注意:當加載程序集后,就無法把它從AppDomain中卸載,只能把整個AppDomain卸載。
當需要在AppDomain加載可執行程序時,可以使用ExecuteAssembly方法。
如,建一個控制台程序:
class Program { static void Main(string[] args) { Console.WriteLine("供應用程序域執行!"); Console.ReadKey(); } }
將上面程序生成的路徑保存到:C:\Users\ChenZhuo\Desktop\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe
下面我們創建一個應用程序域並執行上面那個程序集:
public class Program { static void Main(string[] args) { var domain = AppDomain.CreateDomain("MyAppDomain");
//執行指定的程序集 domain.ExecuteAssembly(@"C:\Users\ChenZhuo\Desktop\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe"); Console.ReadKey(); } }
輸出結果如下:
三、卸載應用程序域
通過Unload可以卸載AppDomain,在AppDomain卸載時將會觸發DomainUnload事件。
下面使用CreateDomain建立一個名為NewAppDomain的應用程序域。然后建立AssemblyLoad事件處理方法,在程序集加載時顯示程序集的信息。最后建立DomainUnload事件處理方法,在AppDomain卸載時顯示卸載信息。
class Program { static void Main(string[] args) { //新建一個應用程序域 AppDomain MyDomain = AppDomain.CreateDomain("domain"); //建立AssemblyLoad事件處理方法 MyDomain.AssemblyLoad += (sender, e) => { Console.WriteLine("程序集正在加載!" + e.LoadedAssembly.FullName); }; //建立AssemblyUnload事件處理方法 MyDomain.DomainUnload += (sender, e) => { Console.WriteLine("程序集正在卸載!"); }; //加載程序集 MyDomain.Load("MySpace"); Thread.Sleep(1000); Console.WriteLine("正在工作中!"); //卸載程序集 AppDomain.Unload(MyDomain); Console.ReadKey(); } }
輸出結果如下:
四、在AppDomain中建立程序集中指定類的對象
使用CreateInstance方法,能建立程序集中指定類的對象。但使用此方法將返回一個ObjectHandle對象,若要將此值轉換為原類型,可調用UnWrap方法。
下面例子利用MySpace.dll程序集中的MySpace.Person對象。
namespace ConsoleApplication1 { public class Program { static void Main(string[] args) { var person = (Person)AppDomain.CurrentDomain.CreateInstance("MySpace", "MySpace.Person").Unwrap(); person.Id = 1; person.Name = "張飛"; Console.WriteLine(person.Id + " : " + person.Name); Console.ReadKey(); } } }
Person:
namespace MySpace { public class Person { public int Id { get; set; } public string Name { get; set; } } }
輸出結果如下: