AppDomain
先從傳統的Windows進程說起,傳統的進程用來描述一組資源和程序運行所必需的內存分配。對于每個被加載到內存的可執行程序,在她的生命周期中操作系統會為之單獨且隔離的進程。由于一個進程的失敗不會影響其他的進程,使用這種方式,運行庫環境將更加穩定。
而一個.NET的應用程序并非直接承載于一個傳統的Windows進程中,而是承載在進程的一個邏輯分區中,術語稱應用程序域(簡稱AppDomain)。一個進程可以擁有多個應用程序域,應用程序域的全部目的就是提供隔離性。
一個AppDomain中的代碼創建的對象不能由另一個AppDomain中的代碼直接訪問。AppDomain是一組程序集的邏輯容器,CLR初始化時創建的第一個AppDomain稱為"默認AppDomain",這個默認的AppDomain只有在Windows進程終止時才會被銷毀。
●AppDomain可以卸載。
●AppDomain可以單獨保護。AppDomain在創建后,會應用一個權限集,它決定了在這個AppDomain中運行的程序集的最大權限。
●AppDomain可以單獨實施配置。AppDomain在創建后,會關聯一組配置設置。這些設置主要影響CLR在AppDomain中加載程序集的方式。這些設置涉及搜索路徑,版本綁定重定向,卷影復制及加載器優化。
相比較與傳統的:
1.應用程序域是.NET平臺操作系統獨立性的關鍵特性。這種邏輯分區將不同操作系統表現加載可執行程序的差異抽象化了。
2.和一個完整的進程相比,應用程序域的CPU和內存占用要小的多。
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中的對象構造出來的新對象也是從屬于默認上下文中。
? ?
? ?
? ?
? ?
? ?
?參考文章
進程、應用程序域、程序集、對象上下文
總結
- 上一篇: 用Redux来进行组件间通讯
- 下一篇: mac安全与隐私只有两个选项,少了一个任