服务行为 之 并发与实例化
本章节介绍 服务行为中的 并发与实例化行为,服务行为的介绍可以参考 《 WCF Behaviors(行为)》
并发与实例化的引入,相关的名词解释
并发(Concurrency):对同时执行的任务数量,度量单位为任务(如请求、作业、事务等);
执行时间(Execution Time):完成任务所用的时间,度量单位为时间(如秒、毫秒等);
吞吐量(Throughput):固定时间内完成的任务数量,度量单位为 任务/时间 如:请求/秒、事务/分钟;
提高吞吐量的办法:
- 减少执行时间
通过修改任务内部算法或增加硬件资源来完成,WCF在此方面作用有限。
- 提高并发
通过提高单位时间内的并发数量, WCF中控制另发的两个行为:InstanceContextMode 和 ConcurrencyMode
ServiceBehavior 属性不定义在接口上,而是服务类上,因为ServiceBehavior属性针对的是服务的行为,而不是服务的契约。
InstanceContextMode 服务行为用于控制实例化,取值说明:
- Single : 所有入站请求均共用同一个服务类的实例;
- PerCall :为每一个入站请求创建一个服务类的实例;
- PerSession:为每一个会话创建一个服务类的实例,默认值;
InstanceContextMode 的默认值为PerSession,但当使用无会话的信道(通道)或者无会话绑定时,会变化为PerCall行为。
ConcurrencyMode 服务行为用于控制服务实例内的线程并发,取值说明:
Single:同一时刻仅能有一个线程访问服务类,默认值;
Reentrant:同一个是仅能有一个线程访问服务类,但该线程可以暂时离开服务类进入等待状态,而后又激活继续访问;
Multiple:多线程并发访问服务类。
Single 是最安全的设置,服务操作不需要担心线程安全问题,Multiple 需要类具有内置的线程安全能力。
同时使用 InstanceContextMode 与 ConcurrencyMode 这两个设置,可以配置服务的实例化和并发来满足特定的性能需求。
会话实例 - 单线程 - 默认-示例
定义服务接口
[ServiceContract] public interface IStockService { [OperationContract] double GetPrice(string ticker); }
实现服务,输出线程ID
public class StockService : IStockService { public StockService() { Console.WriteLine("{0}:Created new instance of StockService on thread", DateTime.Now); } #region IStockService Members public double GetPrice(string ticker) { Console.WriteLine("{0}: GetPrice called on thread {1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId); return 94.95; } #endregion }
客户端异步调用代码
public static void GetPriceWithAsync(string address, Binding binding) { //IStockService proxy = CreateStockServiceProxy(address, binding); StockServiceClient proxy = new StockServiceClient(stockContext, binding, new EndpointAddress(address)); IAsyncResult arGetPrice; for (int i = 0; i < 3; i++) { Console.WriteLine("{0} : Calling GetPrice", DateTime.Now); arGetPrice = proxy.BeginGetPrice("msft", GetPriceCallback, proxy); Thread.Sleep(100);//为了让输出更清晰 Interlocked.Increment(ref c); } while (c > 0) { Thread.Sleep(1000); } stockProxy.Close(); } static void GetPriceCallback(IAsyncResult ar) { double price = ((StockServiceClient)ar.AsyncState).EndGetPrice(ar); Console.WriteLine("{0}: GetPrice result:{1}", DateTime.Now, price); Interlocked.Decrement(ref c); }
执行结果
结果分析:
1. 对每个客户端的请求,服务端仅生成了一个服务实例
2. 每个请求都在各自的线程中被执行。
以上代码采用net.tcp方式进行绑定,net.tcp支持会话,因此InstanceContextMode取值为PerSession;
ConcurrencyMode.Single 设置使得每个WCF对于每个服务实例只允许一个线程。
多实例 - 单线程 - 示例
服务类代码:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)] public class StockService : IStockService
执行结果
结果分析:
1. 为每个请求创建了一个实例;
2. 每个请求在各自的线程中处理请求。
InstanceContextMode.PerSession 在绑定不支持会话的情况下会演变为 InstanceContextMode.PerCall ,故如果采用BasicHttpBinding方式,则无须进行设置。
单实例-多线程-示例
适用情况:
服务初始化代价高昂,如:构造方法需要从数据库中加载大量数据,或者构建一个大的数据结构;
服务代码中存在锁机制,可以保护线程局部安全,提高伸缩性;
服务类代码:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)] public class StockService : IStockService
执行结果:
结果分析:
1. 服务端仅创建了一次实例;
2. 每次请求是在不同的线程中执行的;
单实例 - 单线程 - 单例模式
适用情况:没有锁机制,要求先进先出处理方式,调用者存在共享情况
服务端代码:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)] public class StockService : IStockService
执行结果:
结果分析:
1. 与 会话实例 - 单线程 - 示例中不同的是,显式声明 InstanceContextMode = InstanceContextMode.Single 后,一旦host成功,自动会创建服务实例;
2. 多个请求重用一个线程,
ConcurrencyMode.Single 设置使得WCF在同一时刻只允许运行一个服务类的一个实例,并不控制创建线程的总量
会话级实例
要实现基于会话的服务实例,必须要实现两点:
1. 在契约级打开会话功能:SessionMode : Allowed|NotAllowed|Requied;
2. 在服务级设置按会话创建服务实例;
虽然会话是在契约级别设置是否允许,但其真正实现是在绑定元素指定的信道,因此服务第一次启动时会校验契约行为与信道的兼容性,如果信道需要会话,而绑定不支持会话(如:basicHttpBinding),则会抛出异常
会话实例 - 多线程 - 示例
代码:
[DataContract] public class StockPrice { [DataMember] public double Price; [DataMember] public int CallsCounter; } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)] public class StockService : IStockService { public StockService() { Console.WriteLine("{0}:Created new instance of StockService on thread", DateTime.Now); } public StockPrice GetStockPrice(string ticker) { StockPrice p = new StockPrice(); Console.WriteLine("{0}: GetPrice called on thread {1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId); p.Price = 94.85; lock (locker) { p.CallsCounter = ++callsCounter; } Thread.Sleep(1000); return p; } }
执行结果:启动两个客户端,同时调用
结果分析:
1. 创建了两个实例,分别针对两个客户端;
2. 计数器针对两个客户端是独立的,分别计数;
3. 服务端处理请求在3个线程中进行,属于多线程。
如果将InstanceContextMode改为Single,则计数器将从1增长到6
使用配置 控制 实例、线程
通过配置对程序中 的多实例、多线程、会话数进行控制
maxConcurrentInstances : 最大并发实例数,当InstanceContextMode取值为 PerSession或PerCall 时此配置会起作用,Single表示只存在一个实例;
maxConcurrentCalls:最大并发调用数量,当 ConcurrencyMode 为 Mutiple时起作用;
maxConcurrentSeesions:最大会话数量,当InstanceContextMode取值为PerSession时起作用,当达到最大数量时,会等待其他会话关闭。