第七篇:并发模型与实例模型
在以往使用WebService时,针对每一个请求,服务类总是并发响应的,并且对每个请求都生成新的实例。在WCF中,情况发生变化了,它允许服务发布者自定义并组合并发模型与实例模型。
并发模型有三种:
ConcurrencyMode Single: 单线程模型,可以理解为,针对一个客户端,只有一个线程负责响应; Reentrant:可重入的单线程模型,与Single的区别在于,对于OneWay/回调,它不会阻塞,而是把回调的线程放到队列尾部等着最后处理; Multiple: 多线程模型,可以理解为,针对一个客户端,也允许并发访问; |
实例模型也有三种:
InstanceContextMode PerCall: 针对每次调用都生成新的服务实例; PerSession:针对一个会话生成一个服务实例; Single: 针对所有会话和所有调用共用同一个服务实例; |
组合起来是什么效果呢?我们来用示例代码验证一下。
1、服务端
服务契约,具体解释见实现类吧,只要注意一下Sleep方法定义成了IsOneWay=true:
using System;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace Server
{
[ServiceContract(Namespace = "WCF.Demo")]
publicinterface IData
{
[OperationContract]
int GetCounter();
[OperationContract(IsOneWay = true)]
void Sleep();
}
}
契约实现类:
using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Threading;
using System.Net;
namespace Server
{
//Single并发模式 + PerCall实例模式,针对后面的测试要修改这两个值的组合
[ServiceConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)]
publicclass DataProvider : IData
{
//定义一个计数器,对每个新生成的服务实例,它都是0,我们通过它来判断是否新实例
publicint Counter { get; set; }
//获取计数器,并自增计数器
publicint GetCounter()
{
return ++Counter;
}
//睡眠2秒
publicvoid Sleep()
{
Thread.Sleep(2000);
}
}
}
App.config不列了,用的是netTcpBinding。
2、客户端:
别的不列了,只列一下调用代码:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
namespace Client
{
class Program
{
staticvoid Main(string[] args)
{
//启动3个线程并发访问
for(int i = 0; i < 3; ++i)
{
var thread = new Thread(() =>
{
string name = Thread.CurrentThread.Name;
var proxy = new ChannelFactory<Server.IData>("DataProvider").CreateChannel();
//先调用GetCounter方法,再调用Sleep方法,然后再调一次GetCounter方法
Console.WriteLine(string.Format("{0}: {1} {2}", name, proxy.GetCounter(), DateTime.Now.ToString("HH:mm:ss.fff")));
proxy.Sleep();
Console.WriteLine(string.Format("{0}: {1} {2}", name, proxy.GetCounter(), DateTime.Now.ToString("HH:mm:ss.fff")));
((IChannel)proxy).Close();
});
//定义一下线程名,方便识别
thread.Name = "线程" + i;
thread.Start();
}
}
}
}
OK,开始验证:
1、ConcurrencyMode.Single + InstanceContextMode.PerCall
执行结果如下:
线程1: 1 15:56:05.262
线程2: 1 15:56:05.262
线程0: 1 15:56:05.263
线程1: 1 15:56:07.263
线程2: 1 15:56:07.263
线程0: 1 15:56:07.264
首先,打印出的Counter全是1,说明针对每次请求,服务端的契约实现类(DataProvider)都是新实例化的。其次,同一个线程的两次GetCounter请求相隔了2秒,说明针对一个客户端的调用阻塞了。再次,三个线程几乎同时完成调用,说明它们之间并未互相阻塞。
2、ConcurrencyMode.Single + InstanceContextMode.PerSession
执行结果如下:
线程0: 1 16:02:46.173
线程1: 1 16:02:46.173
线程2: 1 16:02:46.173
线程1: 2 16:02:48.174
线程2: 2 16:02:48.174
线程0: 2 16:02:48.174
与上面相比,区别在于同一个线程的Counter在第二次调用时变成2了,说明针对同一个客户端的两次调用使用的是同一个服务实例。
3、ConcurrencyMode.Single + InstanceContextMode.Single
执行结果如下:
线程1: 2 16:05:46.270
线程0: 1 16:05:46.270
线程2: 3 16:05:46.270
线程1: 4 16:05:52.273
线程0: 5 16:05:52.273
线程2: 6 16:05:52.274
与上面相比,区别在于Counter一直在增长,这说明在服务端自始至终只有一个服务实例,它来响应所有的会话所有的请求。
4、ConcurrencyMode.Reentrant + InstanceContextMode.PerCall
执行结果如下:
线程1: 1 16:07:42.505
线程2: 1 16:07:42.506
线程2: 1 16:07:42.507
线程1: 1 16:07:42.507
线程0: 1 16:07:42.505
线程0: 1 16:07:42.507
和1的区别在于两次GetCounter调用之间没有2秒的延迟,这是由于Reentrant模式下,回调被放入队列尾部再处理,不会阻塞后面的调用。并且针对同一客户端的每个请求都是不同的服务实例在处理,不会阻塞。
5、ConcurrencyMode.Reentrant + InstanceContextMode.PerSession
执行结果如下:
线程2: 1 16:27:44.699
线程0: 1 16:27:44.700
线程1: 1 16:27:44.699
线程0: 2 16:27:46.700
线程1: 2 16:27:46.700
线程2: 2 16:27:46.700
与上面相比,区别在于又有了2秒的阻塞,这是由于针对一个客户端的多次请求,是同一个服务实例在处理,虽然允许重入,但只有一个对象,执行顺序是:第一次GetCounter->Sleep(不阻塞)->Sleep回调->第二次GetCounter,所以表现上还是阻塞住了。
6、ConcurrencyMode.Reentrant + InstanceContextMode.Single
执行结果如下:
线程0: 1 16:34:01.417
线程1: 3 16:34:01.418
线程2: 2 16:34:01.417
线程0: 4 16:34:05.420
线程1: 5 16:34:07.420
线程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
执行结果如下:
线程2: 1 17:07:05.639
线程1: 1 17:07:05.639
线程0: 1 17:07:05.639
线程2: 1 17:07:05.640
线程1: 1 17:07:05.641
线程0: 1 17:07:05.641
多次调用完全是并发的,每次调用的实例也是新创建的。
8、ConcurrencyMode.Multiple + InstanceContextMode.PerSession
执行结果如下:
线程1: 1 17:09:10.285
线程0: 1 17:09:10.285
线程1: 2 17:09:10.286
线程0: 2 17:09:10.286
线程2: 1 17:09:10.285
线程2: 2 17:09:10.287
多次调用完全并发,但针对同一个会话,实例是相同的。
9、ConcurrencyMode.Multiple + InstanceContextMode.Single
执行结果如下:
线程1: 1 17:16:46.543
线程0: 3 17:16:46.543
线程2: 2 17:16:46.543
线程1: 4 17:16:46.544
线程0: 5 17:16:46.544
线程2: 6 17:16:46.544
完全并发,Counter也一直增长,表明自始至终是同一个服务实例。
本文出自 “” 博客,请务必保留此出处