WCF初探-28:WCF中的併發

理解WCF中的併發機制

 

  • 在對WCF併發機制進行理解時,必須對WCF初探-27:WCF中的實例化進行理解,由於WCF中的併發特色是伴隨着服務實例上下文實現的。WCF的實例上下文模型能夠經過InstanceContext的屬性來進行設置,WCF中的併發就是指一個實例上下文處理請求消息的能力,當須要在一個實例上下文中處理多個消息請求時就會產生併發。因此當InstanceContextMode的值爲PerSession或Single的時候就會產生併發的狀況,這時咱們能夠經過設置ConcurrencyMode的值來控制服務併發處理消息的模式。
  • ConcurrencyMode指定服務類是支持單線程仍是多線程操做模式。具備如下三個值:
  1. Single:服務實例是單線程的,且不接受可重入調用。若是 InstanceContextMode 屬性爲 Single,且其餘消息在實例處理調用的同時到達,則這些消息必須等待,直到服務可用或消息超時爲止。
  2. Reentrant:服務實例是單線程的,且接受可重入調用。可重入服務接受在調用其餘服務的同時進行調用;所以在調出以前,您須要負責讓對象的狀態一致,而在調出以後,必須確認本地操做數據有效。請注意,只有經過通道調用其餘服務,才能解除服務實例鎖定。在此狀況下,已調用的服務能夠經過回調重入第一個服務。若是第一個服務不可重入,則該調用順序會致使死鎖。
  3. Multiple:服務實例是多線程的。無同步保證。由於其餘線程能夠隨時更改服務對象,因此必須始終處理同步與狀態一致性。

 

理解併發模式與實例上下文模式的關係

 

  • 當咱們的InstanceContextMode設置爲PerSession時,一個客戶端會話模型就會產生一個服務實例上下文,此時若是咱們的ConcurrencyMode值設置爲Single,那麼服務將以串行的模式進行消息處理,由於併發模式採用的是單線程模式,因此一次只會處理一個消息,而且同一個實例上下文模型中的線程ID同樣。
  • 當咱們把InstanceContextMode設置爲PerSession,ConcurrencyMode值設置爲Multiple時,服務採用多線程處理模型。即一個實例上下文中會出現多個線程來處理消息請求,這樣就大大加快程序處理的能力,但不是絕對的。程序的處理能力加大就會對服務器產生消耗,因此在對消息進行併發處理時,咱們也要對處理的最大能力進行限制。而已採用多線程模型處理消息時,必定要保證線程時安全的。(這一部分須要各位多線程進行友好的理解)
  • 什麼狀況下咱們纔會遇到ConcurrencyMode爲Reentrant的狀況呢?Single採用的是單線程處理模型,當客戶端調用服務端方法時,就會給方法加上鎖,若是此時服務端需對客戶端產生回調,而且回調的方法採用的是請求\應答的消息模型,當服務對客戶端調用完成後,就會嘗試從新獲取實例上下文模型對接下來的程序邏輯進行處理,而且會對實例上下文進行加鎖,可是此時的實例上下文在以前已經被鎖住了。回調以前的實例上下文只有在消息處理完成後才能釋放鎖,而回調後的實例上下文又不能得到鎖,致使操做永遠沒法完成,這就產生了死鎖。此時就能夠將ConcurrencyMode設置爲Reentrant解決此問題。(也能夠將ConcurrencyMode設置爲Multiple解決此問題,由於設置爲Multiple後採用的是多線程處理模型)

 

WCF中的併發模型示例

 

  注意:之後此係列博文若是無特別說明,解決方案都按以前的Client、Host、Service方式進行創建,客戶端代理類採用工具Scvutil.exe生成,若是不清楚能夠參考此係列以前的博文。html

 

  • 此示例採用InstanceContextMode = PerSession, ConcurrencyMode = Single的組合模型,即一個會話產生一個實例上下文,一個實例上下文只有一個線程處理請求。咱們經過輸出處理請求的實例上下文和線程ID就能夠說明此模型的處理情形。參考代碼以下:
   using System.ServiceModel;
    using System.Threading;

    namespace Service
    {
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
        public class SampleMethod:ISampleMethod
        {

            public string MethodOne(string msg)
            {
                OperationContext operationContext = OperationContext.Current;
                InstanceContext instanceContext = operationContext.InstanceContext;
                string info = "InstanceContext HashCode: " + instanceContext.GetHashCode().ToString();
                System.Threading.Thread.Sleep(2000);
                return "You called MethodOne return message is: " + msg + "\n->" + info + "\n->ManagedThreadId:" + 
              Thread.CurrentThread.ManagedThreadId.ToString()+" "+System.DateTime.Now.ToString(); } public string MethodTwo(string msg) { OperationContext operationContext = OperationContext.Current; InstanceContext instanceContext = operationContext.InstanceContext; string info = "InstanceContext HashCode: " + instanceContext.GetHashCode().ToString(); System.Threading.Thread.Sleep(3000); return "You called MethodTwo return message is: " + msg + "\n->" + info + "\n->ManagedThreadId:" +
              Thread.CurrentThread.ManagedThreadId.ToString() + " " + System.DateTime.Now.ToString(); } } }

 

   客戶端採用異步方式進行處理,因此Svcutil.exe必須生成異步客戶端(svcutil.exe /out:f:\SampleMethodClient.cs /config:f:\App.confighttp://localhost:1234/SampleMethod /a /tcv:Version35),客戶端參考代碼以下: 安全

    using System;
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                SampleMethodClient client1 = new SampleMethodClient();
                client1.MethodOneCompleted += new EventHandler<MethodOneCompletedEventArgs>(client1_MethodOneCompleted);
                client1.MethodOneAsync("Client1 called MethodOne");
                client1.MethodTwoCompleted += new EventHandler<MethodTwoCompletedEventArgs>(client1_MethodTwoCompleted);
                client1.MethodTwoAsync("Client1 called MethodTwo");

                SampleMethodClient client2 = new SampleMethodClient();
                client2.MethodOneCompleted += new EventHandler<MethodOneCompletedEventArgs>(client2_MethodOneCompleted);
                client2.MethodOneAsync("Client2 called MethodOne");
                client2.MethodTwoCompleted += new EventHandler<MethodTwoCompletedEventArgs>(client2_MethodTwoCompleted);
                client2.MethodTwoAsync("Client2 called MethodTwo");
    
                Console.Read();
            }


            static void client2_MethodOneCompleted(object sender, MethodOneCompletedEventArgs e)
            {
                Console.WriteLine(e.Result.ToString());
            }

            static void client2_MethodTwoCompleted(object sender, MethodTwoCompletedEventArgs e)
            {
                Console.WriteLine(e.Result.ToString());
            }

            static void client1_MethodOneCompleted(object sender, MethodOneCompletedEventArgs e)
            {
                Console.WriteLine(e.Result.ToString());
            }
            static void client1_MethodTwoCompleted(object sender, MethodTwoCompletedEventArgs e)
            {
                Console.WriteLine(e.Result.ToString());
            }
        }
    }

 

  運行結果:服務器

  

  結果說明:多線程

  Client1處理MethodOne和MethodTwo的時間點不一樣,可是處理的實例上下文ID和線程ID是一致的,這驗證了上面的組合處理模型,而且因爲客戶端產生了兩個代理,併發

  創建了兩個會話,因此Client1和Client2的實例上下文ID不一致。異步

 

  • 接下來,咱們將示例採用InstanceContextMode = PerSession, ConcurrencyMode = Multiple的組合模型,讓服務採用多線程併發模式處理,代碼只須要將 ConcurrencyMode的值改成Multiple,其餘的不變,從新編譯運行程序。

      運行結果:工具

  

 

  結果說明:spa

  Client1處理MethodOne和MethodTwo的時間點不一樣,雖然處理的實例上下文ID一致,但線程ID是不一致的,這驗證了上面的組合處理模型,而且因爲客戶端產生了兩個代理,線程

  創建了兩個會話,因此Client1和Client2的實例上下文ID不一致。3d

相關文章
相關標籤/搜索