先從傳統的Windows進程提及,傳統的進程用來描述一組資源和程序運行所必需的內存分配。對於每一個被加載到內存的可執行程序,在她的生命週期中操做系統會爲之單獨且隔離的進程。因爲一個進程的失敗不會影響其餘的進程,使用這種方式,運行庫環境將更加穩定。 跨域
而一個.NET的應用程序並不是直接承載於一個傳統的Windows進程中,而是承載在進程的一個邏輯分區中,術語稱應用程序域(簡稱AppDomain)。一個進程能夠擁有多個應用程序域,應用程序域的所有目的就是提供隔離性。 數組
一個AppDomain中的代碼建立的對象不能由另外一個AppDomain中的代碼直接訪問。AppDomain是一組程序集的邏輯容器,CLR初始化時建立的第一個AppDomain稱爲"默認AppDomain",這個默認的AppDomain只有在Windows進程終止時纔會被銷燬。安全
●AppDomain能夠卸載。ide
●AppDomain能夠單獨保護。AppDomain在建立後,會應用一個權限集,它決定了在這個AppDomain中運行的程序集的最大權限。優化
●AppDomain能夠單獨實施配置。AppDomain在建立後,會關聯一組配置設置。這些設置主要影響CLR在AppDomain中加載程序集的方式。這些設置涉及搜索路徑,版本綁定重定向,卷影複製及加載器優化。this
相比較與傳統的: spa
1.應用程序域是.NET平臺操做系統獨立性的關鍵特性。這種邏輯分區將不一樣操做系統表現加載可執行程序的差別抽象化了。 操作系統
2.和一個完整的進程相比,應用程序域的CPU和內存佔用要小的多。 .net
3.應用程序域爲承載的應用程序提供了深度的隔離。一個失敗,其餘不會失敗。線程
每一個AppDomain都有一個Loader堆,每一個Loader堆記錄了AppDomain自建立以來訪問過的類型,每一個類型都有一個方法表,方法表的每一個記錄項都指向Jit編譯的本地代碼(前提是該方法至少執行過一次)。
有的程序集原本就要由多個AppDomain使用,最典型的例子就是MSCorLib.dll。該程序集包含了System.Object,System.Int32以及其餘全部與.Net Framework密不可分的類型。CLR初始化時,該程序集會自動加載,並且全部的AppDomain都共享該程序集的類型。爲了減小資源的消耗,MSCorLib.dll程序集以一種"AppDomain中立"的方式加載。也就是說,針對以"AppDomain中立"方式加載的程序集,CLR會爲它們維護一個特殊的Loader堆。該Loader堆中全部的類型對象,以及爲這些類型定義的方法JIT編譯生成的全部本地代碼,都會被進程中的全部AppDomain共享。針對此段文字的描述,AppDomain中立則不像是一個AppDomain,而是一個相似於AppDomain的區域(或結構)記錄着與AppDomain類似的信息。
跨越AppDomain邊界訪問對象
可以跨域AppDomain訪問的對象,必定要經過兩種方式,其一是按值封送,另外一個是按引用封送。除此以外非封送類型進行跨越AppDomain訪問時會拋出異常。下面例子參考《CLE via C#》。
// 該類的實例可跨越AppDomain的邊界"按引用封送" public sealed class MarshalByRefType : MarshalByRefObject { public MarshalByRefType() { Console.WriteLine("{0} .ctor running in {1}", this.GetType().Name, Thread.GetDomain().FriendlyName); } public void SomeMehtod() { Console.WriteLine("Executing is " + Thread.GetDomain().FriendlyName); } }
先定義一個可按引用封送的類型,再經過如下代碼試驗
exeAssmeply是當前默認AppDomain的包含Main方法的程序集,即 Assembly.GetEntryAssembly().FullName
執行結果是
*** Demo #1
MarshalByRefType .ctor running in AD #2
Type=AppDomainLib.MarshalByRefType
Is Proxy=True
Executing is AD #2
Fall Call
接下來是按值封送,對可按引用封送的類修改一下,並增長一個可按值封送的類
// 該類的實例可跨越AppDomain的邊界"按引用封送" public sealed class MarshalByRefType : MarshalByRefObject { public MarshalByRefType() { Console.WriteLine("{0} .ctor running in {1}", this.GetType().Name, Thread.GetDomain().FriendlyName); } public void SomeMehtod() { Console.WriteLine("Executing is " + Thread.GetDomain().FriendlyName); } public MarshalByValType MethodWidthReturn() { Console.WriteLine("Executing is " + Thread.GetDomain().FriendlyName); MarshalByValType t = new MarshalByValType(); return t; } } // 該類的實例可跨越AppDomain的邊界"按值封送" [Serializable] public sealed class MarshalByValType : Object { private DateTime m_CreateTime = DateTime.Now;//注意DateTime是可序列化的 public MarshalByValType() { Console.WriteLine("{0} ctor running in {1},create on {2}", this.GetType().ToString(), Thread.GetDomain().FriendlyName, m_CreateTime); } public override string ToString() { return m_CreateTime.ToLongDateString(); } }
試驗代碼以下
這裏的按值封送類型是須要經過被另外一個AppDomain的實例在非當前AppDomain中構造,故須要屬於AD2的對象mbrt進行構造。返回來的結果則是與本來構造的結果徹底是兩個獨立的對象,按值封送其實是把原有對象進行序列化,傳到目標AppDomain時再反序列化,相似於一個深複製的對象,封送後的對象與原有的對象沒有任何關聯。故運行結果以下
*** Demo #2
MarshalByRefType .ctor running in AD #2
Executing is AD #2
AppDomainLib.MarshalByValType ctor running in AD #2,create on 2012/07/06 16:24:07
Is Porxy=False
Return Object create:2012年月日
Return Object create:2012年月日
最後試驗一下不可封送類型
// 該類的實例可跨越AppDomain的邊界"按引用封送" public sealed class MarshalByRefType : MarshalByRefObject { public MarshalByRefType() { Console.WriteLine("{0} .ctor running in {1}", this.GetType().Name, Thread.GetDomain().FriendlyName); } public void SomeMehtod() { Console.WriteLine("Executing is " + Thread.GetDomain().FriendlyName); } public NonMarshalableType MethodArgAndReturn(string callingDomainName) { // 注意callingDomainName是能夠序列化的 Console.WriteLine("Calling from {0} to {1} ", callingDomainName, Thread.GetDomain().FriendlyName); NonMarshalableType t = new NonMarshalableType(); return t; } } // 該類的實例不可跨越AppDomain進行封送 //[Serializable] public sealed class NonMarshalableType : Object { public NonMarshalableType() { Console.WriteLine("Executing in {0}", Thread.GetDomain().FriendlyName); } }
一樣它的構造都是須要靠屬於另外的AppDomain的對象在另外一個AppDomain中進行構造,試驗代碼與結果以下
*** Demo #3
MarshalByRefType .ctor running in AD #2
Calling from AppDomainLib.vshost.exe to AD #2
Executing in AD #2
'System.Runtime.Serialization.SerializationException' 例外發生。。。
按引用封送
當CreateInstanceAndUnwrap發現它封送的對象類型派生自MarshalByRefObject,CLR就會跨AppDomain邊界按引用封送對象。下面講述了按引用將一個對象從一個AppDomain(源AppDomain,這裏是真正建立對象的地方)封送到另外一個AppDomain(目標AppDomain,這裏是調用CreateInstanceAndUnwrap的地方)的具體含義。
源AppDomain想向目標AppDomain發送或返回一個對象的引用時,CLR會在目標AppDomain的Loader堆中定義一個代理類型。這個代理類型是用原始類型的元數據生成的。所以,他和原始數據看起來徹底同樣。有同樣的實例成員(事件,屬性,方法)。可是實例成員不會成爲代理類型的一部分。在這個代理類型中,確實定義了本身的幾個實例字段,但這些實例字段和原始數據不一致。相反,這些字段只是用於指出那個AppDomain"擁有"真實的對象,以及如何在擁有對象的AppDomain中找到真實的對象。(在內部,代理對象用一個GCHandle實例引用真實的對象)
這個代理類型在目標AppDomain中定義好以後,CreateInstanceAndUnwrap方法就會建立這個代理類型的實例,初始化它的字段來標識源AppDomain和真實對象,而後將對這個代理對象的引用返回目標AppDomain。CLR通常不容許將一個類型的對象轉換成一個不兼容的類型。但在當前這種狀況下,CLR容許轉型,由於新類型和源類型有相同的實例成員。事實上,用代理對象調用GetType方法,他會向你撒謊,說本身是一個MarshalByRefObject對象。System.Runtime.Remoting.RemotingServices.IsTransparentProxy方法能夠用來驗證這個對象是一個代理對象。
AppDomain的Unload靜態方法會強制CLR卸載指定的AppDomain(包括其中加載的程序集),並強制執行一次垃圾回收,以釋放由卸載AppDomain中的代碼建立的對象。這時,默認的AppDomain中mbrt變量仍然引用了一個有效的代理對象。但代理對象已再也不引用一個有效的AppDomain了(它已經被卸載了)。當試圖再次使用代理對象調用SomeMethod方法時,代理的SomeMethod方法會拋出一個AppDomainUnloadedException異常。
因爲新建立的AppDomain是沒有根的,因此代理引用的原始對象能夠被垃圾回收器回收。這固然不理想。但另外一方面,若是將原始對象不肯定的留在內存中,代理可能再也不引用它,而原始對象依然存活,這一樣不理想。CLR解決這個問題的辦法是使用一個"租約管理器"。一個對象的代理建立好以後,CLR保持對象存活5分鐘,若是5分鐘以內沒有經過代理髮出調用,對象就會失效,下次垃圾回收會釋放它的對象。每發出一次對對象的調用,"租約管理器"都會續訂對象的租期,保證它在接下來的2分鐘在內存中保持存活。若是在對象過時以後試圖經過一個代理調用它,CLR會拋出一個System.Runtime.Remoting.RemotingException。默認的5分鐘和2分鐘是能夠修改的,你只須要重寫MarshalByRefObject的InitializeLifetimeService方法。更多的詳情,能夠參看SDK文檔的"生存期租約"主題。
按值封送
按值封送的類型,須要實現Serializable特性。源AppDomain想向目標AppDomain發送或返回一個對象的引用時,CLR將對象的實例字段序列化成一個字節數組。這個字節數組從源AppDomain複製到目標AppDomain。而後在目標AppDomain中反序列化字節數組,這會強制CLR將定義了"被反序列化的類型"的程序集加載到目標AppDomain中(若是還未加載的話)。接着,CLR建立類型的一個實例,並用字節數組中的值初始化對象的字段,使之與原對象的值相同。換言之,CLR在目標AppDomain中複製了源對象。而後CreateInstanceAndUnwrap返回對這個副本的引用;這樣一來,對象就跨AppDomain的邊界按值封送了。按值封送不會涉及代理,返回的對象被默認的AppDomain"擁有"。
最後額外說起一下對象上下文,
應用程序域是承載.NET程序集的進程的邏輯分區。與此類似,應用程序域也能夠進一步被劃分爲多個上下文邊界(context boundary)。事實上,.NET上下文爲單獨的應用程序域提供了一種方式,該方式能爲一個給定對象創建"特定的家"(specific home)。
使用上下文,CLR能夠確保在運行時有特殊需求的對象,能夠經過攔截進出上下文的方法調用,獲得適當的和一致的處理。這個攔截層容許CLR調整當前的方法調用,以便知足給定上下文的設定要求。好比,若是定義一個C#類型須要自動線程安全(使用【Synchronization】特性),CLR將會在分配期間建立"同步上下文"。
和一個進程定義了默認的應用程序域同樣,每個應用程序域都有一個默認的上下文(context 0)。大多數.NET對象都會被加載到上下文0中。若是CLR判斷一個新建立的對象有特殊需求,一個新的上下文邊界將會在承載它的應用程序域中被建立。
能夠經過Thread.CurrentContext得到上下文,經過context的ContextProperties屬性得到描述。同時檢查對象是否被跨上下文訪問時能夠經過RemotingServices.IsObjectOutOfContext(obj)方法進行判斷,跨上下文去訪問對象也經過代理對象進行訪問。如同跨AppDomain中的按引用封送。
可是從屬於另外一個Context中的對象構造出來的新對象也是從屬於默認上下文中。
參考文章