【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的顺序并不一致。

 

不知道用这个示例来装逼以后,各位是否能理解“可重入”并发的含义。

示例源代码下载地址

相关文章
相关标签/搜索