【WCF】服務併發中的「可重入模式」

WCF服務實例的併發模式是在服務實現類上,使用 ServiceBehaviorAttribute 的 ConcurrencyMode 屬性來指定。其值由 ConcurrencyMode 枚舉來界定,這個枚舉只有三個值:併發

Single——服務實例只在單個線程上運行,若是服務是單個實例,那麼同一時間只有一個傳入的調用被接收,其餘調用請拿票排隊。ui

Multiple——這個好理解,服務實例支持多個線程同時調用,因此狀態數據可能會不一樣步(單個服務實例),若是某些變量擔憂被其餘線程意外修改,能夠適合地 lock 一下。spa

Reentrant——這傢伙是今天的主角,由於它不太好理解。老周就不抄MSDN了,就按我本身的理解說一下。「可重入模式」大體是這個意思:首先,服務實例是單線程,可是,若是在服務中調用另外的服務,那麼此時其餘正在排隊的傳入消息就能夠進來。等其餘另外一個服務調用完成後,又從新進入當前服務操做繼續向下執行。有點像你去營業廳排隊辦業務,服務窗口中的工做人員就是服務實例,而窗口外面排隊的客戶就是等待調用的客戶端。假如我要申請一個業務,一般要填個XXX表格。要是等你填完,估計後面排隊的人會跑掉一半。因此,常規的作法是:你站到一邊去填表(至關於在服務實例中調用另一個服務),而後讓後面排隊的人繼續辦業務。等你填完表了,再回來找工做人員處理(至關於另一個服務調用完成,從新進入當前服務實例)。線程

 

沒看懂?仍是實例好用吧。來,下面我們來動動手吧。rest

首先咱們弄個「另外一個」服務。code

    [ServiceContract]
    public interface ISome
    {
        [OperationContract]
        Guid GetUID();
    }

    class SomeService : ISome
    {
        public Guid GetUID()
        {
            Guid id = Guid.NewGuid();
            Console.WriteLine($"第二個服務被調用,產生的ID爲:{id}");
            return id;
        }
    }

 這個服務協定有一個方法,做用很簡單,產生一個GUID,而後返回,能看懂吧。blog

 

好,如今來弄「主」服務。ip

    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        void TestCall();
    }

 

待會兒咱們實現這個協定時,在服務操做方法中去調用前面的「另外一個」服務。get

    internal class TestService : ITestService
    {
        public void TestCall()
        {
            Console.WriteLine("即將調用另外一個服務。");
            // 調用其餘服務
            ISome cnl = ChannelFactory<ISome>.CreateChannel(new BasicHttpBinding(), new EndpointAddress("http://localhost:12345"));
            // 調用完成後,再次回來
            Guid id = cnl.GetUID();
            ((IClientChannel)cnl).Close();
            Console.WriteLine($"回到當前服務。獲得的ID爲:{id}");
        }
    }

 

還沒完呢,咱們設置一下這個服務類,讓它使用「可重入」併發模式。同步

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    internal class TestService : ITestService
    {
         ……
    }

 

啓動ServiceHost並接收請求。

            using (ServiceHost host = new ServiceHost(typeof(TestService)))
            {
                host.AddServiceEndpoint(typeof(ITestService), new WSHttpBinding(), "http://localhost:9000");
                host.Open(); //運行服務
                Console.WriteLine("主服務已啓動。");
                ……
                Console.ReadKey();
            }

 

模擬客戶端調用,我們開N個 Task 來模擬同時有 N 個客戶端調用服務的情形。

                Action actdlg = () =>
                  {
                      ITestService cn = ChannelFactory<ITestService>.CreateChannel(new WSHttpBinding(), new EndpointAddress("http://localhost:9000"));
                      cn.TestCall();
                      // 關閉通道
                      ((IClientChannel)cn).Close();
                  };
                // 開啓5個任務
                Task[] tasks = new Task[5];
                // 初始化每一個任務
                for (int n = 0; n < tasks.Length; n++)
                {
                    tasks[n] = new Task(actdlg);
                }
                // 開始執行任務
                foreach (Task t in tasks)
                {
                    t.Start();
                }
                // 等待全部任務完成
                Task.WaitAll(tasks);

 最後的 Task.WaitAll 用以等待全部 Task 完成執行,此處能夠不要這句。

 

好,見證的時刻即未來了。運行!

 

在當前服務去調用另一個服務的時候,其餘正在等待的調用就會進來,在上圖中,上面的是「另外一個」服務被調用時生成的 GUID,下面是「另外一個」服務調用完成後返回到當前服務後獲得的 GUID。

從圖中,你們會發現,「另外一個」服務產生ID輸出的順序,與調用返回後輸出的順序不一樣,看來,調用完成後,從新進入到當前服務實例的消息還得排隊,故調用後返回的消息順序與「另外一個」服務生成ID的順序並不一致。

 

不知道用這個示例來裝逼以後,各位是否能理解「可重入」併發的含義。

示例源代碼下載地址

相關文章
相關標籤/搜索