1. 程式人生 > 實用技巧 >WCF初探-28:WCF中的併發

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生成,如果不清楚可以參考此係列之前的博文。

  • 此示例採用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,其他的不變,重新編譯執行程式。

執行結果:

  

 

  結果說明:

  Client1處理MethodOne和MethodTwo的時間點不同,雖然處理的例項上下文ID一致,但執行緒ID是不一致的,這驗證了上面的組合處理模型,並且由於客戶端產生了兩個代理,

  建立了兩個會話,所以Client1和Client2的例項上下文ID不一致。