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的順序並不一致。
不知道用這個示例來裝逼以後,各位是否能理解「可重入」併發的含義。