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不一致。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM