應(yīng)用程序域
應(yīng)用程序域(通常簡(jiǎn)稱為AppDomain)可以視為一種輕量級(jí)進(jìn)程。一個(gè)Windows進(jìn)程內(nèi)可以包含多個(gè)AppDomain。AppDomain這個(gè)概念的提出是為了實(shí)現(xiàn)在一個(gè)物理服務(wù)器中承載多個(gè)應(yīng)用程序,并且這些應(yīng)用能夠相互獨(dú)立。ASP.NET中利用AppDomain在同一個(gè)進(jìn)程內(nèi)承載了多組Web應(yīng)用程序就是一個(gè)例子。實(shí)際上微軟曾進(jìn)行過(guò)在單一進(jìn)程內(nèi)承載多達(dá)1000個(gè)簡(jiǎn)單Web應(yīng)用程序的壓力測(cè)試。
使用AppDomain所獲得的性能優(yōu)勢(shì)主要體現(xiàn)在兩方面:
- 創(chuàng)建AppDomain所需要的系統(tǒng)資源比創(chuàng)建一個(gè)Windows進(jìn)程更少。
- 同一個(gè)Windows進(jìn)程內(nèi)所承載的AppDomain之間可以互相共享資源,如CLR、基本.NET類型、地址空間以及線程。
而各個(gè)AppDomain之間的獨(dú)立性體現(xiàn)為以下這些特征:
- 一個(gè)AppDomain可以獨(dú)立于其他的AppDomain而被卸載。
- 一個(gè)AppDomain無(wú)法訪問(wèn)其他AppDomain的程序集和對(duì)象。
- 若沒(méi)有發(fā)生跨邊界的異常拋出,一個(gè)AppDomain擁有自己獨(dú)立的異常管理策略。這意味著一個(gè)AppDomain內(nèi)出現(xiàn)問(wèn)題不會(huì)影響到同一個(gè)進(jìn)程內(nèi)中的其他AppDomain。
- 每個(gè)AppDomain可以分別定義獨(dú)自的程序集代碼訪問(wèn)安全策略。
- 每個(gè)AppDomain可以分別定義獨(dú)自的規(guī)則以便CLR在加載前定位程序集所在位置。
可以看出應(yīng)用程序域是進(jìn)程中的一個(gè)子單元,不過(guò)在.NET中還存在一個(gè)比應(yīng)用程序域還要細(xì)粒度的單元——.NET上下文(Context)。
.NET Context
一個(gè).NET 應(yīng)用程序域能夠包含多個(gè)被稱為.NET上下文的實(shí)體。所有.NET對(duì)象都存在于上下文中,每個(gè)應(yīng)用程序域中至少存在一個(gè)上下文。這個(gè)上下文稱為應(yīng)用程序域的默認(rèn)上下文,它在應(yīng)用程序域創(chuàng)建的時(shí)候就創(chuàng)建了。下圖總結(jié)了它們之間的關(guān)系:

那么MessageSink與上下文有什么關(guān)系呢? 我們知道在通常情況下,如果訪問(wèn)同一個(gè)AppDomain中對(duì)象的方法時(shí),會(huì)采用基于棧的方式(詳見本系列上部)。在這種情況下,我們是無(wú)法攔截其中的消息的,因?yàn)榇藭r(shí)根本不存在消息對(duì)象。只有當(dāng)我們通過(guò)Transparent Proxy訪問(wèn)另一個(gè)對(duì)象的方法時(shí),才會(huì)采用基于消息的方式。而現(xiàn)在我們只知道當(dāng)一個(gè)對(duì)象調(diào)用處在另一個(gè)AppDomain中的遠(yuǎn)程對(duì)象(該對(duì)象為MarshalByRefObject子類)時(shí),Remoting才會(huì)為調(diào)用方創(chuàng)建那個(gè)遠(yuǎn)程對(duì)象的Transparent Proxy。在了解了.NET上下文的概念后,你會(huì)發(fā)現(xiàn),即使處在同一個(gè)AppDomain中的兩個(gè)對(duì)象,如果它們所處的上下文不同,在訪問(wèn)對(duì)方的方法時(shí),也會(huì)借由Transparent Proxy實(shí)現(xiàn),即采用基于消息的方法調(diào)用方式。此時(shí),我們就可以在上下文中插入MessageSink了。那么在上下文中是否存在類似IClientChannelSinkProvider的接口呢?很幸運(yùn),在經(jīng)過(guò)一番探索后,我們發(fā)現(xiàn)確實(shí)存在類似的接口,而且還不止一個(gè):IContributeEnvoySink、IContributeServerContextSink、IContributeObjectSink、IContributeClientContextSink這四個(gè)接口中各自包含了一個(gè)GetXXXSink的方法,它們都會(huì)返回一個(gè)實(shí)現(xiàn)了IMessageSink接口的對(duì)象。我們知道IClientChannelSinkProvider接口是配合配置文件最終實(shí)現(xiàn)向Pipeline中加入ChannelSink的,而以上這四個(gè)接口并沒(méi)有配合配置文件使用,不過(guò)與IClientChannelSinkProvider的使用方式倒也有異曲同工之效。讀者可以將下面這幅圖與本系列上部中的圖2比較一番。

在上圖中又出現(xiàn)了很多新的概念,下面將對(duì)它們一一做出解釋:
ContextBoundObject
上下文可以看作應(yīng)用程序域中一個(gè)包含對(duì)象和消息接收器的區(qū)域。對(duì)上下文里的對(duì)象的調(diào)用會(huì)轉(zhuǎn)換成可以被MessageSink(消息接收器)攔截和處理的消息。我們知道要把調(diào)用轉(zhuǎn)換成消息,必須通過(guò)透明代理這個(gè)中介。而且,僅當(dāng)對(duì)象是MarshalByRefObject的子類的實(shí)例并被其所在的應(yīng)用程序域以外的實(shí)體調(diào)用時(shí),CLR才會(huì)為它創(chuàng)建透明代理。這里,我們希望對(duì)所有調(diào)用使用消息接收器機(jī)制,即使那些調(diào)用是來(lái)自同一個(gè)應(yīng)用程序域中的實(shí)體。這個(gè)時(shí)候我們就需要用到System.ContextBoundObject類了。繼承自ContextBoundObject的類的實(shí)例同樣僅能由透明代理訪問(wèn)。此時(shí),即使在這個(gè)類的方法中使用的this引用也是透明代理而不是對(duì)這個(gè)對(duì)象的直接引用。我們會(huì)發(fā)現(xiàn)ContextBoundObject類繼承自MarshalByRefObject,這非常合理,因?yàn)樗芎玫貜?qiáng)調(diào)了該類的特性——它告訴CLR這個(gè)類將會(huì)通過(guò)透明代理使用。
ContextBoundObject的子類的實(shí)例被視為上下文綁定的(context-bound)。沒(méi)有繼承自ContextBoundObject的類的實(shí)例則被視為上下文靈活的(context-agile)。上下文綁定的對(duì)象永遠(yuǎn)在其上下文中執(zhí)行。只要不是遠(yuǎn)程對(duì)象,上下文靈活的對(duì)象總是在執(zhí)行這個(gè)調(diào)用的上下文中執(zhí)行。如下圖所示:

ContextAttribute
上下文attribute是應(yīng)用在上下文綁定的類上的.NET attribute。上下文attribute類實(shí)現(xiàn)了System.Runtime.Remoting.Contexts.IContextAttribute接口。上下文綁定的類可以應(yīng)用多個(gè)上下文attribute。在這個(gè)類的對(duì)象創(chuàng)建期間,這個(gè)類的每個(gè)上下文attribute判斷這個(gè)對(duì)象的創(chuàng)建者所在的上下文是否適用。該操作通過(guò)以下方法完成:
public bool IContextAttribute.IsContextOK(Context clientCtx,
IConstructionCallMessage ctorMsg)
只要其中一個(gè)上下文attribute返回false,CLR就必須創(chuàng)建一個(gè)新的上下文來(lái)容納這個(gè)新的對(duì)象。這樣,每個(gè)上下文attribute可以在這個(gè)新的上下文中注入一個(gè)或多個(gè)上下文屬性。這些注入通過(guò)以下方法完成:
public void IContextAttribute.GetPropertiesForNewContext(
IConstructionCallMessage ctorMsg)
IContextProperty
上下文屬性是實(shí)現(xiàn)System.Runtime.Remoting.Contexts.IContextProperty接口的類的實(shí)例。每個(gè)上下文可以包含多個(gè)屬性。上下文屬性在上下文創(chuàng)建的時(shí)候通過(guò)上下文attribute注入。一旦每個(gè)上下文attribute注入了它的屬性,就會(huì)為每個(gè)屬性調(diào)用下面的方法。此后就無(wú)法在這個(gè)上下文中注入另外的屬性了:
public void IContextProperty.Freeze( Context ctx )
然后,CLR通過(guò)調(diào)用下面的方法判斷新的上下文能否滿足每個(gè)屬性:
public bool IContextProperty.IsNewContextOK( Context ctx )
每個(gè)上下文屬性都有一個(gè)通過(guò)Name屬性定義的名稱:
public string IContextProperty.Name{ get }
上下文中承載的對(duì)象的方法可以通過(guò)調(diào)用下面的方法訪問(wèn)上下文屬性:
IContextProperty Context.GetProperty( string sPropertyName )
這一點(diǎn)很有意思,上下文中的對(duì)象通過(guò)它們所在的上下文的屬性可以共享信息并訪問(wèn)服務(wù)。不過(guò),上下文屬性的主要作用并不在于此。上下文屬性的主要作用在于向相關(guān)上下文中的消息接收器區(qū)域注入消息接收器(MessageSink)。(消息接收器區(qū)域的概念將在后面介紹)
以上注入MessageSink的過(guò)程可以用下圖概括:

MessageSink Region
不知你是否記得之前提到的四個(gè)接口:IContributeEnvoySink、IContributeServerContextSink、IContributeObjectSink、IContributeClientContextSink。其實(shí)它們分別代表了四個(gè)不同的消息接收器區(qū)域:服務(wù)器(server)區(qū)域、對(duì)象(object)區(qū)域、信使(envoy)區(qū)域和客戶端(client)區(qū)域。要理解區(qū)域概念,你必須考慮上下文綁定的對(duì)象是否被位于另一個(gè)上下文的實(shí)體調(diào)用。這個(gè)實(shí)體可以是一個(gè)靜態(tài)方法或者另一個(gè)對(duì)象。在我們關(guān)于區(qū)域的討論中,我們把這個(gè)實(shí)體所在的上下文稱為調(diào)用方上下文(calling context),而把被調(diào)用對(duì)象所在的上下文稱為目標(biāo)上下文(target context)。目標(biāo)上下文中的每個(gè)屬性都可以在這些區(qū)域中注入消息接收器。
- 注入服務(wù)器區(qū)域的消息接收器攔截所有從另一個(gè)上下文發(fā)往目標(biāo)上下文中所有對(duì)象的調(diào)用消息。于是,每個(gè)目標(biāo)上下文有一個(gè)服務(wù)器區(qū)域。
- 注入對(duì)象區(qū)域的消息接收器攔截所有從另一個(gè)上下文發(fā)往目標(biāo)對(duì)象中特定對(duì)象的調(diào)用消息。于是,上下文中每個(gè)對(duì)象會(huì)有一個(gè)對(duì)象區(qū)域。
- 注入信使區(qū)域的消息接收器攔截所有從另一個(gè)上下文發(fā)往目標(biāo)對(duì)象中特定對(duì)象的調(diào)用消息。信使區(qū)域和對(duì)象區(qū)域的不同點(diǎn)是信使區(qū)域位于調(diào)用方上下文而不是包含對(duì)象的目標(biāo)上下文。我們使用信使區(qū)域把調(diào)用方上下文的信息傳遞給目標(biāo)上下文的消息接收器。
- 注入客戶端區(qū)域的消息接收器攔截所有從目標(biāo)上下文發(fā)往位于其他上下文的對(duì)象的調(diào)用消息。于是,每個(gè)目標(biāo)上下文有一個(gè)客戶端區(qū)域。你可能會(huì)對(duì)這個(gè)區(qū)域所處的位置有點(diǎn)困惑,似乎當(dāng)它位于Calling context的信使區(qū)域下方時(shí)會(huì)顯得更加對(duì)稱。之所以會(huì)有這樣的誤解,是因?yàn)槲覀儗?duì)Server、Client的理解有了偏差。你應(yīng)該記住除了信使區(qū)域是位于Calling context外,另外三個(gè)區(qū)域都是處在Target Context。而所謂的Server,Client是針對(duì)處在Target Context中的對(duì)象在某不同時(shí)刻所扮演不同角色而言的。當(dāng)然Calling context中也會(huì)有客戶端區(qū)域,不過(guò)其中的MessageSink不是通過(guò)Target context的屬性注入的,而應(yīng)該依靠Calling context中的上下文屬性注入。

下載清晰版本
上圖說(shuō)明了區(qū)域的概念。目標(biāo)上下文包含名為OBJ1和OBJ2的兩個(gè)對(duì)象。我們選擇在目標(biāo)上下文中放置兩個(gè)對(duì)象而不是一個(gè)是為了更好地說(shuō)明對(duì)象區(qū)域和信使區(qū)域是在對(duì)象層面與消息的攔截關(guān)聯(lián)起來(lái)的,而服務(wù)器區(qū)域和客戶端區(qū)域則是在上下文層面與消息的攔截關(guān)聯(lián)起來(lái)的。
我們?cè)诿總€(gè)區(qū)域中放置了兩個(gè)自定義消息接收器是為了更好地說(shuō)明一個(gè)區(qū)域能包含零個(gè)、一個(gè)或多個(gè)消息接收器。具體地說(shuō),所有自定義消息接收器都通過(guò)目標(biāo)上下文的屬性注入?yún)^(qū)域,即使這個(gè)區(qū)域不屬于目標(biāo)上下文。因?yàn)槟憧梢远x你自己的上下文屬性類,你可以選擇必須注入哪個(gè)消息接收器。
你可能注意到每個(gè)區(qū)域都包含一個(gè)用于通知CLR退出區(qū)域的系統(tǒng)終結(jié)器接收器(system terminator sink),它是由Remoting框架定義的,并且總是位于每個(gè)區(qū)域的末尾。
當(dāng)調(diào)用方上下文和目標(biāo)上下文處在同一個(gè)應(yīng)用程序域中時(shí),CLR會(huì)使用mscorlib.dll中CrossContextChannel內(nèi)部類的實(shí)例作為信道。這個(gè)實(shí)例會(huì)使得當(dāng)前線程的Context屬性發(fā)生切換。圖中也展示了這一實(shí)例。
系列文章
浙公網(wǎng)安備 33010602011771號(hào)