176
技術社區[雲棲]
ConcurrencyMode.Multiple 模式下的WCF服務就一定是並發執行的嗎:探討同步上下文對並發的影響[下篇]
在《上篇》中,我通過一個具體的實例演示了WCF服務宿主的同步上下文對並發的影響,並簡單地介紹了同步上下文是什麼東東,以及同步上下文在多線程中的應用。那麼,同步上下文在WCF並發體係的內部是如何影響服務操作的執行的呢?這實際上涉及到WCF的一個話題,即線程的親和性(Thread Affinity),本篇文章將為你剖析WCF線程親和機製的本質。
一、WCF線程親和性(Thread Affinity)
對於服務端來說,WCF消息監聽和接收體係通過IO線程池並發的處理來自客戶端的服務調用請求,所以並發抵達的服務調用請求消息能夠得到及時的處理。但是,服務操作具體在那個線程線程執行,則是通過WCF的並發處理體係決定的。
在默認的情況下,WCF采用這樣的機製控製並發操作的執行:如果在進行服務寄宿(IIS寄宿方式除外)的過程中,當前線程存在同步上下文,會將其保存在服務端分發運行時。等到需要執行服務操作的時候,WCF並發體係會判斷分發運行時的同步上下文是否存在,如果不存在則在各個的線程中執行服務操作,否則,服務操作會被封送到該同步上下文中執行。
如果我們將某個服務寄宿於一個控製台(Console)應用之中,由於控製台程序的當前同步上下文為空,按照上麵的並發操作執行機製,所有的請求操作會在各自的線程中並行地執行。所以,在流量允許的範圍內,並發的請求能夠得到及時地處理。
如果我們通過Windows Forms應用作為某個服務的宿主,服務操作的執行永遠是以同步的方式執行的。也就是說,在某個時刻,僅僅隻有針對某個服務調用的服務操作被執行,其他的調用請求都將被放入一個等待隊列中。如果等待的時候超出了設定的超時時限(這在高並發的情況下會比較頻繁),客戶端會拋出TimeoutException異常。我們將服務操作與服務寄宿程序線程自動綁定的現象稱為服務的線程親和性(Thread Affinity)
在這種情況下,由於服務操作執行才UI線程中,可以正常得對Windows窗體上的控件進行操作。如果,你希望服務操作能夠並發地被執行,就不得不打破這種線程親和性,我們可以按照監控程序中的方式在服務類型上應用ServiceBehaviorAttribute特性將UseSynchronizationContext屬性設置成False。但是在這種情況下,如果你需要對控件進行操作,你就需要調用Control類型的Invoke或者BeginInvoke方法,或者按照我們監控程序的方式借助於SynchronizationContext對象了。UseSynchronizationContext在ServiceBehaviorAttribute的定義如下麵的代碼所示,該屬性的默認值為True。
1: [AttributeUsage(AttributeTargets.Class)]
2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
3: {
4: //其他成員
5: public bool UseSynchronizationContext { get; set; }
6: }
通過上麵的介紹,我想讀者對WCF的並發請求操作的執行機製有了一個大概的了解,接下來我們對該機製在WCF並發框架體係下的真正實現進行更加深層次的探討。
二、同步上下文如何影響服務操作的執行?
當服務端信道層成功介紹到來其客戶端的請求消息後,會將該消息遞交給相應信道監聽器(Channel Listener)所在的信道分發器(Channel Dispatcher)。信道分發器則根據相應的消息篩選(Message Filter)將消息進一步分發給匹配的終結點分發器(Endpoint Dispatcher)。終結點分發器根據自己的分發運行時(Dispatch Runtime)設定的處理行為對請求消息執行進一步的處理。關於消息分發、篩選機製,以及分發運行時的創建,在《WCF技術剖析(卷1)》的第2章和第7章有詳細的介紹。
分發運行時控製了終結點分發器進行消息處理的行為,實際上我們大部分作用於服務端自定義行為(契約行為、操作行為、服務行為和終結點行為)都是通過對該運行時進行相應的定製,使得WCF服務端框架按照我們希望的方式處理請求的消息。分發運行時通過System.ServiceModel.Dispatcher.DispatchRuntime類型表示,在DispatchRuntime中定義了如下一個SynchronizationContext屬性。
1: public sealed class DispatchRuntime
2: {
3: //其他成員
4: public SynchronizationContext SynchronizationContext { get; set; }
5: }
如果讀者對《WCF技術剖析(卷1)》第7章關於服務寄宿的整個流程不那麼陌生的話,應該知道DispatchRuntime是在服務寄宿過程中被初始化的。在DispatchRuntime初始化過程中,會按照如下所示的偽代碼對SynchronizationContext進行初始化。初始化邏輯比較簡單,首先會遍曆當前AppDomain中是否加載了名稱以“System.Web,”為前綴的程序集,如果這樣的程序集被成功加載,並且HostingEnvironment的IsHosted返回值為True,則將SynchronizationContext設置為Null。該步驟主要是判斷服務寄宿的方式是否為IIS,因為這樣的寄宿方式不需要同步上下文。實際上,如果你采用ASP.NET應用作為宿主,下麵的代碼也是進行與IIS寄宿一樣的邏輯分支。如果不滿足上麵的條件,則將當前線程的同步上下文賦值給SynchronizationContext屬性。所以,對於Windows Forms應用作為服務的宿主,DispatchRuntime的SynchronizationContext將會被初始化成一個WindowsFormsSynchronizationContext對象。
1: public sealed class DispatchRuntime
2: {
3: //其他成員
4: public SynchronizationContext SynchronizationContext { get; set; }
5: public DispatchRuntime()
6: {
7: //其他操作
8: Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
9: for (int i = 0; i < assemblies.Length; i++)
10: {
11: if (string.Compare(assemblies[i].FullName, 0, "System.Web,", 0, "System.Web,".Length, StringComparison.OrdinalIgnoreCase) == 0)
12: {
13: if (HostingEnvironment.IsHosted)
14: {
15: this.SynchronizationContext = null;
16: return;
17: }
18: }
19: }
20: this.SynchronizationContext = SynchronizationContext.Current;
21: }
22: }
如果對《WCF技術剖析(卷1)》第9章介紹的關於服務整個過程的剖析還不曾遺忘的話,應該知道在初始化DispatchRuntime之後,開啟ServiceHost之後,還有一項重要的操作就是應用所有相關的分發行為。具體來講,就是遍曆所有相關的契約行為、操作行為、服務行為和終結點行為,調用ApplyDispatchBehavior對象。那麼,這肯定涉及到對ServiceBehaviorAttribute的ApplyDispatchBehavior方法的調用。
在ServiceBehaviorAttribute的ApplyDispatchBehavior方法中,會根據UseSynchronizationContext屬性值對DispatchRuntime的SynchronizationContext屬性進行重新設定。具體來講,如果UseSynchronizationContext屬性為False,會將SynchronizationContext設置為NULL。相應的邏輯可以通過下麵的偽代碼表示:
1: public class ServiceBehaviorAttribute : Attribute, IServiceBehavior
2: {
3: //其他成員
4: public bool UseSynchronizationContext { get; set; }
5: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
6: {
7: //其他操作
8: if (!UseSynchronizationContext)
9: {
10: foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
11: {
12: foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
13: {
14: endpointDispatcher.DispatchRuntime.SynchronizationContext = null;
15: }
16: }
17: }
18: }
19: }
當真正的服務調用請求被分發給終結點分發器後,會先判斷DispatchRuntime的SynchronizationContext是否存在。如果返回為NULL,請求消息會在各自的線程中進行處理,否則,會將後續的消息處理操作奉送到該SynchronizationContext表示的同步上下文中執行。因此,在DispatchRuntime的SynchronizationContext存在的情況下,後續的消息處理過程都是以同步的方式執行的。終結點分發器對請求消息的處理大體上可以通過下麵一段偽代碼表示:
1: public void ProcessMessage(Message message)
2: {
3: SendOrPostCallback messageProcessCallback = GetMessageProcessCallback();
4: DispatchRuntime dispatchRuntime = GetCurrentRuntime();
5: SynchronizationContext context = dispatchRuntime.SynchronizationContext;
6: if (context != null)
7: {
8: context.Post(messageProcessCallback, message);
9: }
10: else
11: {
12: IOThreadScheduler.ScheduleCallback(messageProcessCallback, message);
13: }
14: }
上麵我們談到WCF服務端並發體係基於同步上下文的處理機製,從中我們知道了對於非IIS和ASP.NET的寄宿方式,如果在進行服務寄宿的時候當前線程存在同步上下文(比如Windows Forms應用作為宿主),服務操作最終是在該同步上下文中執行的。
相似的情況向同樣發生在回調操作的執行上麵。在回調場景中,客戶端開啟服務代理並指定回調實例上下文對象進行服務調用的時候,如果當前線程存在同步上下文,那麼當服務端進行回調的時候,回調操作會自動被封送到該同步上下文中執行。也就是說,回調操作與客戶端程序也存在一種線程關聯性。
在服務端,我們可以通過在服務類型上麵應用ServiceBehaviorAttribute特性並將UseSynchronizationContext屬性設置成False,來解除服務操作與服務寄宿程序之間的線程關聯性。在客戶端,我們也可以采用特性標注的方式解除掉回調操作與客戶端程序之間的線程關聯性,而這個特性就是我們之前提到過的CallbackBehaviorAttribute。如下麵的代碼所示,CallbackBehaviorAttribute特性同樣具有一個UseSynchronizationContext屬性。
1: [AttributeUsage(AttributeTargets.Class)]
2: public sealed class CallbackBehaviorAttribute : Attribute, IEndpointBehavior
3: {
4: //其他成員
5: public bool UseSynchronizationContext { get; set; }
6: }
如下麵的代碼所示,我們隻需要在回調類型上應用CallbackBehaviorAttribute特性,並將UseSynchronizationContext設置成False,就可以解除回調操作與客戶端程序之間的線程關聯性。在這種情況下,回調操作將會在接受回調請求的IO線程中執行。
1: [CallbackBehavior(UseSynchronizationContext = false)]
2: public class CalculatorCallbackService : ICalculatorCallback
3: {
4: //省略成員
5: }
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 15:04:46