AppDomain
先從傳統(tǒng)的Windows進(jìn)程說起,傳統(tǒng)的進(jìn)程用來描述一組資源和程序運(yùn)行所必需的內(nèi)存分配。對(duì)于每個(gè)被加載到內(nèi)存的可執(zhí)行程序,在她的生命周期中操作系統(tǒng)會(huì)為之單獨(dú)且隔離的進(jìn)程。由于一個(gè)進(jìn)程的失敗不會(huì)影響其他的進(jìn)程,使用這種方式,運(yùn)行庫環(huán)境將更加穩(wěn)定。
而一個(gè).NET的應(yīng)用程序并非直接承載于一個(gè)傳統(tǒng)的Windows進(jìn)程中,而是承載在進(jìn)程的一個(gè)邏輯分區(qū)中,術(shù)語稱應(yīng)用程序域(簡稱AppDomain)。一個(gè)進(jìn)程可以擁有多個(gè)應(yīng)用程序域,應(yīng)用程序域的全部目的就是提供隔離性。
一個(gè)AppDomain中的代碼創(chuàng)建的對(duì)象不能由另一個(gè)AppDomain中的代碼直接訪問。AppDomain是一組程序集的邏輯容器,CLR初始化時(shí)創(chuàng)建的第一個(gè)AppDomain稱為"默認(rèn)AppDomain",這個(gè)默認(rèn)的AppDomain只有在Windows進(jìn)程終止時(shí)才會(huì)被銷毀。
●AppDomain可以卸載。
●AppDomain可以單獨(dú)保護(hù)。AppDomain在創(chuàng)建后,會(huì)應(yīng)用一個(gè)權(quán)限集,它決定了在這個(gè)AppDomain中運(yùn)行的程序集的最大權(quán)限。
●AppDomain可以單獨(dú)實(shí)施配置。AppDomain在創(chuàng)建后,會(huì)關(guān)聯(lián)一組配置設(shè)置。這些設(shè)置主要影響CLR在AppDomain中加載程序集的方式。這些設(shè)置涉及搜索路徑,版本綁定重定向,卷影復(fù)制及加載器優(yōu)化。
相比較與傳統(tǒng)的:
1.應(yīng)用程序域是.NET平臺(tái)操作系統(tǒng)獨(dú)立性的關(guān)鍵特性。這種邏輯分區(qū)將不同操作系統(tǒng)表現(xiàn)加載可執(zhí)行程序的差異抽象化了。
2.和一個(gè)完整的進(jìn)程相比,應(yīng)用程序域的CPU和內(nèi)存占用要小的多。
3.應(yīng)用程序域?yàn)槌休d的應(yīng)用程序提供了深度的隔離。一個(gè)失敗,其他不會(huì)失敗。
每個(gè)AppDomain都有一個(gè)Loader堆,每個(gè)Loader堆記錄了AppDomain自創(chuàng)建以來訪問過的類型,每個(gè)類型都有一個(gè)方法表,方法表的每個(gè)記錄項(xiàng)都指向Jit編譯的本地代碼(前提是該方法至少執(zhí)行過一次)。
有的程序集本來就要由多個(gè)AppDomain使用,最典型的例子就是MSCorLib.dll。該程序集包含了System.Object,System.Int32以及其他所有與.Net Framework密不可分的類型。CLR初始化時(shí),該程序集會(huì)自動(dòng)加載,而且所有的AppDomain都共享該程序集的類型。為了減少資源的消耗,MSCorLib.dll程序集以一種"AppDomain中立"的方式加載。也就是說,針對(duì)以"AppDomain中立"方式加載的程序集,CLR會(huì)為它們維護(hù)一個(gè)特殊的Loader堆。該Loader堆中所有的類型對(duì)象,以及為這些類型定義的方法JIT編譯生成的所有本地代碼,都會(huì)被進(jìn)程中的所有AppDomain共享。針對(duì)此段文字的描述,AppDomain中立則不像是一個(gè)AppDomain,而是一個(gè)類似于AppDomain的區(qū)域(或結(jié)構(gòu))記錄著與AppDomain相似的信息。
跨越AppDomain邊界訪問對(duì)象
能夠跨域AppDomain訪問的對(duì)象,一定要通過兩種方式,其一是按值封送,另一個(gè)是按引用封送。除此之外非封送類型進(jìn)行跨越AppDomain訪問時(shí)會(huì)拋出異常。下面例子參考《CLE via C#》。
// 該類的實(shí)例可跨越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); }}?
先定義一個(gè)可按引用封送的類型,再通過以下代碼試驗(yàn)
exeAssmeply是當(dāng)前默認(rèn)AppDomain的包含Main方法的程序集,即 Assembly.GetEntryAssembly().FullName
執(zhí)行結(jié)果是
*** Demo #1
MarshalByRefType .ctor running in AD #2
Type=AppDomainLib.MarshalByRefType
Is Proxy=True
Executing is AD #2
Fall Call
? ?
接下來是按值封送,對(duì)可按引用封送的類修改一下,并增加一個(gè)可按值封送的類
// 該類的實(shí)例可跨越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; } }// 該類的實(shí)例可跨越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(); } }?
試驗(yàn)代碼如下
這里的按值封送類型是需要通過被另一個(gè)AppDomain的實(shí)例在非當(dāng)前AppDomain中構(gòu)造,故需要屬于AD2的對(duì)象mbrt進(jìn)行構(gòu)造。返回來的結(jié)果則是與原本構(gòu)造的結(jié)果完全是兩個(gè)獨(dú)立的對(duì)象,按值封送實(shí)際上是把原有對(duì)象進(jìn)行序列化,傳到目標(biāo)AppDomain時(shí)再反序列化,類似于一個(gè)深復(fù)制的對(duì)象,封送后的對(duì)象與原有的對(duì)象沒有任何關(guān)聯(lián)。故運(yùn)行結(jié)果如下
*** 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年月日
? ?
最后試驗(yàn)一下不可封送類型
// 該類的實(shí)例可跨越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; } }// 該類的實(shí)例不可跨越AppDomain進(jìn)行封送 //[Serializable] public sealed class NonMarshalableType : Object {public NonMarshalableType() { Console.WriteLine("Executing in {0}", Thread.GetDomain().FriendlyName); } }?
同樣它的構(gòu)造都是需要靠屬于另外的AppDomain的對(duì)象在另一個(gè)AppDomain中進(jìn)行構(gòu)造,試驗(yàn)代碼與結(jié)果如下
*** Demo #3
MarshalByRefType .ctor running in AD #2
Calling from AppDomainLib.vshost.exe to AD #2
Executing in AD #2
'System.Runtime.Serialization.SerializationException' 例外發(fā)生。。。
按引用封送
? 當(dāng)CreateInstanceAndUnwrap發(fā)現(xiàn)它封送的對(duì)象類型派生自MarshalByRefObject,CLR就會(huì)跨AppDomain邊界按引用封送對(duì)象。下面講述了按引用將一個(gè)對(duì)象從一個(gè)AppDomain(源AppDomain,這里是真正創(chuàng)建對(duì)象的地方)封送到另一個(gè)AppDomain(目標(biāo)AppDomain,這里是調(diào)用CreateInstanceAndUnwrap的地方)的具體含義。
? 源AppDomain想向目標(biāo)AppDomain發(fā)送或返回一個(gè)對(duì)象的引用時(shí),CLR會(huì)在目標(biāo)AppDomain的Loader堆中定義一個(gè)代理類型。這個(gè)代理類型是用原始類型的元數(shù)據(jù)生成的。因此,他和原始數(shù)據(jù)看起來完全一樣。有一樣的實(shí)例成員(事件,屬性,方法)。但是實(shí)例成員不會(huì)成為代理類型的一部分。在這個(gè)代理類型中,確實(shí)定義了自己的幾個(gè)實(shí)例字段,但這些實(shí)例字段和原始數(shù)據(jù)不一致。相反,這些字段只是用于指出那個(gè)AppDomain"擁有"真實(shí)的對(duì)象,以及如何在擁有對(duì)象的AppDomain中找到真實(shí)的對(duì)象。(在內(nèi)部,代理對(duì)象用一個(gè)GCHandle實(shí)例引用真實(shí)的對(duì)象)
? 這個(gè)代理類型在目標(biāo)AppDomain中定義好之后,CreateInstanceAndUnwrap方法就會(huì)創(chuàng)建這個(gè)代理類型的實(shí)例,初始化它的字段來標(biāo)識(shí)源AppDomain和真實(shí)對(duì)象,然后將對(duì)這個(gè)代理對(duì)象的引用返回目標(biāo)AppDomain。CLR一般不允許將一個(gè)類型的對(duì)象轉(zhuǎn)換成一個(gè)不兼容的類型。但在當(dāng)前這種情況下,CLR允許轉(zhuǎn)型,因?yàn)樾骂愋秃驮搭愋陀邢嗤膶?shí)例成員。事實(shí)上,用代理對(duì)象調(diào)用GetType方法,他會(huì)向你撒謊,說自己是一個(gè)MarshalByRefObject對(duì)象。System.Runtime.Remoting.RemotingServices.IsTransparentProxy方法可以用來驗(yàn)證這個(gè)對(duì)象是一個(gè)代理對(duì)象。
? AppDomain的Unload靜態(tài)方法會(huì)強(qiáng)制CLR卸載指定的AppDomain(包括其中加載的程序集),并強(qiáng)制執(zhí)行一次垃圾回收,以釋放由卸載AppDomain中的代碼創(chuàng)建的對(duì)象。這時(shí),默認(rèn)的AppDomain中mbrt變量仍然引用了一個(gè)有效的代理對(duì)象。但代理對(duì)象已不再引用一個(gè)有效的AppDomain了(它已經(jīng)被卸載了)。當(dāng)試圖再次使用代理對(duì)象調(diào)用SomeMethod方法時(shí),代理的SomeMethod方法會(huì)拋出一個(gè)AppDomainUnloadedException異常。
? 由于新創(chuàng)建的AppDomain是沒有根的,所以代理引用的原始對(duì)象可以被垃圾回收器回收。這當(dāng)然不理想。但另一方面,如果將原始對(duì)象不確定的留在內(nèi)存中,代理可能不再引用它,而原始對(duì)象依然存活,這同樣不理想。CLR解決這個(gè)問題的辦法是使用一個(gè)"租約管理器"。一個(gè)對(duì)象的代理創(chuàng)建好之后,CLR保持對(duì)象存活5分鐘,如果5分鐘之內(nèi)沒有通過代理發(fā)出調(diào)用,對(duì)象就會(huì)失效,下次垃圾回收會(huì)釋放它的對(duì)象。每發(fā)出一次對(duì)對(duì)象的調(diào)用,"租約管理器"都會(huì)續(xù)訂對(duì)象的租期,保證它在接下來的2分鐘在內(nèi)存中保持存活。如果在對(duì)象過期之后試圖通過一個(gè)代理調(diào)用它,CLR會(huì)拋出一個(gè)System.Runtime.Remoting.RemotingException。默認(rèn)的5分鐘和2分鐘是可以修改的,你只需要重寫MarshalByRefObject的InitializeLifetimeService方法。更多的詳情,可以參看SDK文檔的"生存期租約"主題。
按值封送
? 按值封送的類型,需要實(shí)現(xiàn)Serializable特性。源AppDomain想向目標(biāo)AppDomain發(fā)送或返回一個(gè)對(duì)象的引用時(shí),CLR將對(duì)象的實(shí)例字段序列化成一個(gè)字節(jié)數(shù)組。這個(gè)字節(jié)數(shù)組從源AppDomain復(fù)制到目標(biāo)AppDomain。然后在目標(biāo)AppDomain中反序列化字節(jié)數(shù)組,這會(huì)強(qiáng)制CLR將定義了"被反序列化的類型"的程序集加載到目標(biāo)AppDomain中(如果還未加載的話)。接著,CLR創(chuàng)建類型的一個(gè)實(shí)例,并用字節(jié)數(shù)組中的值初始化對(duì)象的字段,使之與原對(duì)象的值相同。換言之,CLR在目標(biāo)AppDomain中復(fù)制了源對(duì)象。然后CreateInstanceAndUnwrap返回對(duì)這個(gè)副本的引用;這樣一來,對(duì)象就跨AppDomain的邊界按值封送了。按值封送不會(huì)涉及代理,返回的對(duì)象被默認(rèn)的AppDomain"擁有"。?
最后額外提及一下對(duì)象上下文,
應(yīng)用程序域是承載.NET程序集的進(jìn)程的邏輯分區(qū)。與此相似,應(yīng)用程序域也可以進(jìn)一步被劃分為多個(gè)上下文邊界(context boundary)。事實(shí)上,.NET上下文為單獨(dú)的應(yīng)用程序域提供了一種方式,該方式能為一個(gè)給定對(duì)象建立"特定的家"(specific home)。
使用上下文,CLR可以確保在運(yùn)行時(shí)有特殊需求的對(duì)象,可以通過攔截進(jìn)出上下文的方法調(diào)用,得到適當(dāng)?shù)暮鸵恢碌奶幚怼_@個(gè)攔截層允許CLR調(diào)整當(dāng)前的方法調(diào)用,以便滿足給定上下文的設(shè)定要求。比如,如果定義一個(gè)C#類型需要自動(dòng)線程安全(使用【Synchronization】特性),CLR將會(huì)在分配期間創(chuàng)建"同步上下文"。?
和一個(gè)進(jìn)程定義了默認(rèn)的應(yīng)用程序域一樣,每一個(gè)應(yīng)用程序域都有一個(gè)默認(rèn)的上下文(context 0)。大多數(shù).NET對(duì)象都會(huì)被加載到上下文0中。如果CLR判斷一個(gè)新創(chuàng)建的對(duì)象有特殊需求,一個(gè)新的上下文邊界將會(huì)在承載它的應(yīng)用程序域中被創(chuàng)建。
可以通過Thread.CurrentContext獲得上下文,通過context的ContextProperties屬性獲得描述。同時(shí)檢查對(duì)象是否被跨上下文訪問時(shí)可以通過RemotingServices.IsObjectOutOfContext(obj)方法進(jìn)行判斷,跨上下文去訪問對(duì)象也通過代理對(duì)象進(jìn)行訪問。如同跨AppDomain中的按引用封送。
但是從屬于另一個(gè)Context中的對(duì)象構(gòu)造出來的新對(duì)象也是從屬于默認(rèn)上下文中。
? ?
? ?
? ?
? ?
? ?
?參考文章
進(jìn)程、應(yīng)用程序域、程序集、對(duì)象上下文
總結(jié)
- 上一篇: 用Redux来进行组件间通讯
- 下一篇: Java对象及相关