天天看點

[原創]WCF技術剖析之八:ClientBase<T>中對ChannelFactory<T>的緩存機制

和傳統的分布式遠端調用一樣,WCF的服務調用借助于服務代理(Service Proxy)。而ChannelFactory<T>則是服務代理的建立者。WCF采用基于終結點(Endpoint)服務消費方式:WCF服務通過一個或者多個終結點暴露給潛在的服務消費者,服務的消費中通過與之比對的終結點與之互動。在用戶端,我們具有兩種典型的服務代理建立方式,其一是通過諸如SvcUtil.exe這樣的工具導入服務的中繼資料生成相應的服務代理(一個繼承自ClientBase<T>的類型)代碼和相關配置;其二是直接通過相應的終結點資訊(通過代碼指定或者配置)建立ChannelFactory<T>對象,并借助該對象直接進行服務代理的建立。

實際上,即使通過ClientBase<T>對象進行服務調用,其内部也是調用ChannelFactory<T>建立的服務代理。整個ChannelFactory<T>的建立是一項相對複雜并且費時的工作,會涉及很多諸如反射、配置檔案的讀取等操作。為了提高服務調用的性能,在.NET 3.5中,WCF在ClientBase<T>中引入了ChannelFactory<T>的緩存機制。

一、如何實作對ChannelFactory<T>的緩存

為了讓讀者對ChannelFactory<T>的緩存機制有一個直覺的認識,我們來做一個簡單的實驗:在一個Console應用中執行如下的代碼,其中CalculatorClient可以看成是本節開篇時自定義的服務代理類。在本例中,先後以相同的方式(調用相同的構造函數,傳入相同的參數)建立并開啟了兩個CalculatorClient對象,然後檢驗它們的ChannelFactory是否是相同的對象。

1: CalculatorClient proxy1 = new CalculatorClient("calculateservice");      
2: proxy1.Open();      
3: CalculatorClient proxy2 = new CalculatorClient("calculateservice");      
4: proxy2.Open();Console.WriteLine("object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = {0}",       
5: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory));      

輸出結果:

1: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = True      

從輸出的結果,可以看出兩個不同的ClientBase<T>對象使用了相同的ChannelFactory<T>對象。這得益于在.NET 3.5中新加入的ChannelFactory<T>的緩存機制。那麼,在WCF用戶端架構内部對ChannelFactory<T>的緩存是如何實作的呢?

實際上,ChannelFactory<T>的緩存實作很簡單,被建立出來的ChannelFactory<T>集合通過ClientBase<T>的一個靜态變量儲存起來。我們可以将這個ChannelFactory<T>集合看成是一個字典,字典的值就是ChannelFactory<T>,而鍵則通過下面三個對象派生:

  • CallbackInstance:以InstanceContext對象表示的對回調對象的封裝;
  • EndpointConfigurationName:終結點在配制檔案中的名稱;
  • RemoteAddress:終結點的遠端位址,類型為EndpointAddress。

它們分别與ClienBase<T>構造函數中相應的參數相比對。當調用某個構造函數建立對象的時候,WCF将傳入的三個參數作為Key(如果再構造函數中并未指定相應的參數,會使用預設值,EndpointConfigurationName、CallbackInstance和RemoteAddress的預設值分别為*、null和null),從緩存(靜态變量)中去找比對的ChannelFactory<T>對象,如果成功找到,則直接傳回,否則重新建立,在傳回之前将其放入緩存中。

從這個意義上講,多個ClienBase<T>對象能夠重用相同的ChannelFactory<T>對象的前提是它們使用相同的構造函數,并傳入相同的參數被建立。為了驗證這一點,再來做一個實驗,隻須要将上面的例子稍加修改,通過另一個重載構造函數來建立CalculatorClient對象。

1: CalculatorClient proxy1 = new CalculatorClient("calculateservice",new EndpointAddress("http://127.0.0.1:9999/calculateservice");      
2: proxy1.Open();      
3:        
4: CalculatorClient proxy2 = new CalculatorClient("calculateservice");      
5: proxy2.Open();      
6: Console.WriteLine("object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = {0}",       
7: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory));      
8:        

輸出結果:

1: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = False      

實際上,proxy1和proxy2最終使用的終結點位址是相同的(http://127.0.0.1:9999/ calculatorservice),隻不過一個是通過代碼指定的,另一個則是通過配置檔案配置的。但是,就是因為建立ClienBase<T>時使用了不同的構造函數重載,導緻不能重用同一個ChannelFactory<T>對象。

ChannelFactory<T>的重用避免了頻繁地常見ChannelFactory<T>對象,進而獲得更好的性能。在具體的應用中,我們應該盡可能地利用這樣的機制。但是,由于程式設計人員對ChannelFactory<T>的緩存機制不了解,不知不覺就會使這個緩存機制失效。接下來就來讨論這個問題。

二、ChannelFactory<T>緩存機制的失效

總體來講,下面的兩種情況會引起ChannelFactory<T>緩存機制失效。

  • 在構造函數中傳入綁定對象建構ClientBase<T>;
  • 在ClientBase<T>開啟(調用Open方法)之前,通路如下三個隻讀屬性:ChannelFactory、Endpoint和ClientCredential。

為了加深讀者的了解,我們通過實驗的方式來證明上面的兩種說法。為了驗證在構造函數中傳入綁定對象對ChannelFactory<T>緩存機制的影響,寫了如下的代碼:通過Binding和EndpointAddress對象建立ClienBase<T>對象。

1: Binding binding = new BasicHttpBinding();      
2: EndpointAddress address = new EndpointAddress("http://127.0.0.1:9999/calculateservice");      
3: CalculatorClient proxy1 = new CalculatorClient(binding,address);      
4: proxy1.Open();      
5: CalculatorClient proxy2 = new CalculatorClient(binding, address);      
6: proxy2.Open();      
7: Console.WriteLine("object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = {0}",       
8: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory));      

輸出結果:

1: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = False      

接下來,再通過實驗整個在ClientBase<T>開啟(調用Open方法)之前通路ChannelFactory、Endpoint和ClientCredential三個隻讀屬性對ChannelFactory<T>緩存機制的影響。在這裡,以通路ChannelFactory屬性為例

1: CalculatorClient proxy1 = new CalculatorClient("calculateservice");      
2: ChannelFactory<ICalculator> factory = proxy1.ChannelFactory;      
3: proxy1.Open();      
4: CalculatorClient proxy2 = new CalculatorClient("calculateservice");      
5: proxy2.Open();      
6: Console.WriteLine("object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = {0}",       
7: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory));      
8:        

輸出結果:

1: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = False      

在上面的例子中,在Proxy1的Open方法調用之前,調用了隻讀屬性ChannelFactory,并将其指派到一個臨時變量中,中間根本沒有對ChannelFactory<T>作任何修改,僅僅一次我們認為微不足道的對隻讀屬性的通路就破壞了WCF用戶端架構對ChannelFactory<T>的緩存機制。

三、如何有效利用ChannelFactory<T>的緩存機制

為了能夠充分利用ChannelFactory<T>的緩存機制,獲得更好的服務調用性能,我們可以得出以下兩個最佳實踐:

  • 避免通過人為指定綁定對象建立ClientBase<T>對象,應該盡可能使用配置的綁定資訊;
  • 避免在ClientBase<T>開啟之前讀取ChannelFactory、Endpoint和ClientCredential三個屬性,或者在建立ClientBase<T>之後顯式調用Open方法開啟ClientBase<T>對象。

注:部分内容節選自《WCF技術剖析(卷1)》第八章:用戶端(Clients)

[原創]WCF技術剖析之八:ClientBase&amp;lt;T&amp;gt;中對ChannelFactory&amp;lt;T&amp;gt;的緩存機制

WCF技術剖析系列:

WCF技術剖析之一:通過一個ASP.NET程式模拟WCF基礎架構

WCF技術剖析之二:再談IIS與ASP.NET管道

WCF技術剖析之三:如何進行基于非HTTP的IIS服務寄宿

WCF技術剖析之四:基于IIS的WCF服務寄宿(Hosting)實作揭秘

WCF技術剖析之五:利用ASP.NET相容模式建立支援會話(Session)的WCF服務

WCF技術剖析之六:為什麼在基于ASP.NET應用寄宿(Hosting)下配置的BaseAddress無效

WCF技術剖析之七:如何實作WCF與EnterLib PIAB、Unity之間的內建

WCF技術剖析之八:ClientBase<T>中對ChannelFactory<T>的緩存機制

WCF技術剖析之九:服務代理不能得到及時關閉會有什麼後果?

WCF技術剖析之十:調用WCF服務的用戶端應該如何進行異常處理

WCF技術剖析之十一:異步操作在WCF中的應用(上篇)

WCF技術剖析之十一:異步操作在WCF中的應用(下篇)

WCF技術剖析之十二:資料契約(Data Contract)和資料契約序列化器(DataContractSerializer)

WCF技術剖析之十三:序列化過程中的已知類型(Known Type)

WCF技術剖析之十四:泛型資料契約和集合資料契約(上篇)

WCF技術剖析之十四:泛型資料契約和集合資料契約(下篇)

WCF技術剖析之十五:資料契約代理(DataContractSurrogate)在序列化中的作用

WCF技術剖析之十六:資料契約的等效性和版本控制