第七篇:并发模型与实例模型

在以往使用WebService时,针对每一个请求,服务类总是并发响应的,并且对每个请求都生成新的实例。在WCF中,情况发生变化了,它允许服务发布者自定义并组合并发模型与实例模型。

并发模型有三种:

ConcurrencyMode

Single:   单线程模型,可以理解为,针对一个客户端,只有一个线程负责响应;

           Reentrant:可重入的单线程模型,与Single的区别在于,对于OneWay/回调,它不会阻塞,而是把回调的线程放到队列尾部等着最后处理;
           Multiple: 多线程模型,可以理解为,针对一个客户端,也允许并发访问;

实例模型也有三种:

InstanceContextMode

PerCall:   针对每次调用都生成新的服务实例;

           PerSession:针对一个会话生成一个服务实例;
           Single:    针对所有会话和所有调用共用同一个服务实例;

组合起来是什么效果呢?我们来用示例代码验证一下。

1、服务端

服务契约,具体解释见实现类吧,只要注意一下Sleep方法定义成了IsOneWay=true:

 
  1. using System;

  2. using System.ServiceModel;

  3. using System.ServiceModel.Description;

  4. namespace Server

  5. {

  6.    [ServiceContract(Namespace = "WCF.Demo")]

  7. publicinterface IData

  8.    {

  9.        [OperationContract]

  10. int GetCounter();

  11.        [OperationContract(IsOneWay = true)]

  12. void Sleep();

  13.    }

  14. }

契约实现类:

 
  1. using System;

  2. using System.ServiceModel;

  3. using System.ServiceModel.Description;

  4. using System.Threading;

  5. using System.Net;

  6. namespace Server

  7. {

  8. //Single并发模式 + PerCall实例模式,针对后面的测试要修改这两个值的组合

  9.    [ServiceConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)]

  10. publicclass DataProvider : IData

  11.    {

  12. //定义一个计数器,对每个新生成的服务实例,它都是0,我们通过它来判断是否新实例

  13. publicint Counter { get; set; }

  14. //获取计数器,并自增计数器

  15. publicint GetCounter()

  16.        {

  17. return ++Counter;

  18.        }

  19. //睡眠2秒

  20. publicvoid Sleep()

  21.        {

  22.            Thread.Sleep(2000);

  23.        }

  24.    }

  25. }

App.config不列了,用的是netTcpBinding。

2、客户端:

别的不列了,只列一下调用代码:

 
  1. using System;

  2. using System.ServiceModel;

  3. using System.ServiceModel.Channels;

  4. using System.Threading;

  5. namespace Client

  6. {

  7. class Program

  8.    {

  9. staticvoid Main(string[] args)

  10.        {

  11.            //启动3个线程并发访问

  12. for(int i = 0; i < 3; ++i)

  13.            {

  14.                var thread = new Thread(() =>

  15.                {

  16. string name = Thread.CurrentThread.Name;

  17.                    var proxy = new ChannelFactory<Server.IData>("DataProvider").CreateChannel();

  18.                    //先调用GetCounter方法,再调用Sleep方法,然后再调一次GetCounter方法

  19.                    Console.WriteLine(string.Format("{0}: {1}  {2}", name, proxy.GetCounter(), DateTime.Now.ToString("HH:mm:ss.fff")));

  20.                   proxy.Sleep();

  21.                    Console.WriteLine(string.Format("{0}: {1}  {2}", name, proxy.GetCounter(), DateTime.Now.ToString("HH:mm:ss.fff")));

  22.                    ((IChannel)proxy).Close();

  23.                });

  24.                //定义一下线程名,方便识别

  25.                thread.Name = "线程" + i;

  26.                thread.Start();

  27.            }

  28.        }

  29.    }

  30. }

OK,开始验证:

1、ConcurrencyMode.Single + InstanceContextMode.PerCall

执行结果如下:

 
  1. 线程1: 1  15:56:05.262

  2. 线程2: 1  15:56:05.262

  3. 线程0: 1  15:56:05.263

  4. 线程1: 1  15:56:07.263

  5. 线程2: 1  15:56:07.263

  6. 线程0: 1  15:56:07.264

首先,打印出的Counter全是1,说明针对每次请求,服务端的契约实现类(DataProvider)都是新实例化的。其次,同一个线程的两次GetCounter请求相隔了2秒,说明针对一个客户端的调用阻塞了。再次,三个线程几乎同时完成调用,说明它们之间并未互相阻塞。

2、ConcurrencyMode.Single + InstanceContextMode.PerSession

执行结果如下:

 
  1. 线程0: 1  16:02:46.173

  2. 线程1: 1  16:02:46.173

  3. 线程2: 1  16:02:46.173

  4. 线程1: 2  16:02:48.174

  5. 线程2: 2  16:02:48.174

  6. 线程0: 2  16:02:48.174

与上面相比,区别在于同一个线程的Counter在第二次调用时变成2了,说明针对同一个客户端的两次调用使用的是同一个服务实例。

3、ConcurrencyMode.Single + InstanceContextMode.Single

执行结果如下:

 
  1. 线程1: 2  16:05:46.270

  2. 线程0: 1  16:05:46.270

  3. 线程2: 3  16:05:46.270

  4. 线程1: 4  16:05:52.273

  5. 线程0: 5  16:05:52.273

  6. 线程2: 6  16:05:52.274

与上面相比,区别在于Counter一直在增长,这说明在服务端自始至终只有一个服务实例,它来响应所有的会话所有的请求。

4、ConcurrencyMode.Reentrant + InstanceContextMode.PerCall

执行结果如下:

 
  1. 线程1: 1  16:07:42.505

  2. 线程2: 1  16:07:42.506

  3. 线程2: 1  16:07:42.507

  4. 线程1: 1  16:07:42.507

  5. 线程0: 1  16:07:42.505

  6. 线程0: 1  16:07:42.507

和1的区别在于两次GetCounter调用之间没有2秒的延迟,这是由于Reentrant模式下,回调被放入队列尾部再处理,不会阻塞后面的调用。并且针对同一客户端的每个请求都是不同的服务实例在处理,不会阻塞。

5、ConcurrencyMode.Reentrant + InstanceContextMode.PerSession

执行结果如下:

 
  1. 线程2: 1  16:27:44.699

  2. 线程0: 1  16:27:44.700

  3. 线程1: 1  16:27:44.699

  4. 线程0: 2  16:27:46.700

  5. 线程1: 2  16:27:46.700

  6. 线程2: 2  16:27:46.700

与上面相比,区别在于又有了2秒的阻塞,这是由于针对一个客户端的多次请求,是同一个服务实例在处理,虽然允许重入,但只有一个对象,执行顺序是:第一次GetCounter->Sleep(不阻塞)->Sleep回调->第二次GetCounter,所以表现上还是阻塞住了。

6、ConcurrencyMode.Reentrant + InstanceContextMode.Single

执行结果如下:

 
  1. 线程0: 1  16:34:01.417

  2. 线程1: 3  16:34:01.418

  3. 线程2: 2  16:34:01.417

  4. 线程0: 4  16:34:05.420

  5. 线程1: 5  16:34:07.420

  6. 线程2: 6  16:34:07.420

自始至终只有一个服务实例,执行顺序应该是:线程0的GetCounter->线程2的GetCounter->线程1的GetCounter->线程0的Sleep(不阻塞)->线程2的Sleep(不阻塞)->线程0的Sleep回调->线程1的Sleep(不阻塞)->线程0的GetCounter->线程2的Sleep回调->线程1的Sleep回调->线程1的GetCounter->线程2的GetCounter。挺晕的。

7、ConcurrencyMode.Multiple + InstanceContextMode.PerCall

执行结果如下:

 
  1. 线程2: 1  17:07:05.639

  2. 线程1: 1  17:07:05.639

  3. 线程0: 1  17:07:05.639

  4. 线程2: 1  17:07:05.640

  5. 线程1: 1  17:07:05.641

  6. 线程0: 1  17:07:05.641

多次调用完全是并发的,每次调用的实例也是新创建的。

8、ConcurrencyMode.Multiple + InstanceContextMode.PerSession

执行结果如下:

 
  1. 线程1: 1  17:09:10.285

  2. 线程0: 1  17:09:10.285

  3. 线程1: 2  17:09:10.286

  4. 线程0: 2  17:09:10.286

  5. 线程2: 1  17:09:10.285

  6. 线程2: 2  17:09:10.287

多次调用完全并发,但针对同一个会话,实例是相同的。

9、ConcurrencyMode.Multiple + InstanceContextMode.Single

执行结果如下:

 
  1. 线程1: 1  17:16:46.543

  2. 线程0: 3  17:16:46.543

  3. 线程2: 2  17:16:46.543

  4. 线程1: 4  17:16:46.544

  5. 线程0: 5  17:16:46.544

  6. 线程2: 6  17:16:46.544

完全并发,Counter也一直增长,表明自始至终是同一个服务实例。

本文出自 “” 博客,请务必保留此出处