对象池示例演示如何扩展 Windows Communication Foundation (WCF)以支持对象池。 此示例演示如何创建一个在语法上和语义上类似于 ObjectPoolingAttribute 企业服务的属性功能的属性。 对象池可以极大地提升应用程序的性能。 但是,如果未正确使用,它可能会产生相反的效果。 对象池有助于减少重新创建需要大量初始化的常用对象的开销。 但是,如果对共用对象的方法的调用需要相当长的时间才能完成,则只要达到最大池大小,对象池就会排队其他请求。 因此,它可能无法通过引发超时异常来提供某些对象创建请求。
注释
本示例的设置过程和生成说明位于本主题末尾。
创建 WCF 扩展的第一步是决定要使用的扩展点。
在 WCF 中,术语 调度程序 是指一个运行时组件,负责将传入消息转换为用户服务上的方法调用,以及将返回值从该方法转换为传出消息。 WCF 服务为每个终结点创建调度程序。 如果与 WCF 客户端关联的协定是一个双工协定,则该客户端必须使用调度程序。
通道和终结点调度程序通过公开各种控制调度程序行为的属性来提供通道和合约范围内的扩展性。 该 DispatchRuntime 属性还允许你检查、修改或自定义调度过程。 此示例重点介绍 InstanceProvider 指向提供服务类实例的对象的属性。
IInstanceProvider
在 WCF 中,调度程序使用实现 InstanceProvider 接口的 IInstanceProvider 创建服务类的实例。 此接口有三种方法:
GetInstance(InstanceContext, Message):当消息到达时,Dispatcher 调用 GetInstance(InstanceContext, Message) 方法以创建服务类的实例来处理消息。 对此方法的调用频率由 InstanceContextMode 属性决定。 例如,如果将 InstanceContextMode 属性设置为 PerCall,则会在服务类的每个消息到达时,创建一个新的实例来处理,因此每当消息到达时,就会调用 GetInstance(InstanceContext, Message)。
GetInstance(InstanceContext):这与上一个方法相同,但当没有 Message 参数时,将调用此方法。
ReleaseInstance(InstanceContext, Object):服务实例的生存期已过后,调度程序将调用该方法 ReleaseInstance(InstanceContext, Object) 。 与方法一样 GetInstance(InstanceContext, Message) ,对此方法的调用频率由 InstanceContextMode 属性决定。
对象池
自定义 IInstanceProvider 实现为服务提供所需的对象池语义。 因此,此示例有一个为池提供 ObjectPoolingInstanceProvider 自定义实现的 IInstanceProvider 类型。 当 Dispatcher 调用 GetInstance(InstanceContext, Message) 该方法时,自定义实现会查找内存中池中的现有对象,而不是创建新实例。 如果找到一个对象,则返回该对象。 否则,将创建一个新对象。 在以下示例代码中显示了 GetInstance 的实现。
object IInstanceProvider.GetInstance(InstanceContext instanceContext, Message message)
{
object obj = null;
lock (poolLock)
{
if (pool.Count > 0)
{
obj = pool.Pop();
}
else
{
obj = CreateNewPoolObject();
}
activeObjectsCount++;
}
WritePoolMessage(ResourceHelper.GetString("MsgNewObject"));
idleTimer.Stop();
return obj;
}
自定义 ReleaseInstance 实现将释放的实例添加回池并递减 ActiveObjectsCount 值。
Dispatcher 可以从不同的线程中调用这些方法,因此需要同步访问 ObjectPoolingInstanceProvider 的类级成员。
void IInstanceProvider.ReleaseInstance(InstanceContext instanceContext, object instance)
{
lock (poolLock)
{
pool.Push(instance);
activeObjectsCount--;
WritePoolMessage(
ResourceHelper.GetString("MsgObjectPooled"));
// When the service goes completely idle (no requests
// are being processed), the idle timer is started
if (activeObjectsCount == 0)
idleTimer.Start();
}
}
该方法 ReleaseInstance 提供“清理初始化”功能。 通常,该池为其生存期保持最少数量的对象。 然而,由于使用量过大,有时需要在池中创建额外的对象以达到配置中指定的最大限制。 最终,当池变得不是很活跃时,那些多余的对象可能成为额外的开销。 因此,当 activeObjectsCount 达到零时,将启动一个空闲计时器来触发并执行清理周期。
添加行为
使用下面的行为将调度程序层扩展挂钩:
服务行为。 这些允许自定义整个服务运行时。
终结点行为。 这些允许自定义服务终结点,特别是通道和终结点调度程序。
合同行为。 这些允许分别在客户端和服务上自定义两者 ClientRuntime 以及 DispatchRuntime 类。
出于对象池扩展的目的,必须创建一个服务特性。 服务行为是通过实现 IServiceBehavior 接口创建的。 可通过多种方式使服务模型了解自定义行为:
使用自定义属性。
强制将它添加到服务说明的行为集合中。
扩展配置文件。
此示例使用自定义属性。 ServiceHost构造时,它会检查服务类型定义中使用的属性,并将可用行为添加到服务说明的行为集合中。
该接口IServiceBehavior有三种方法 -- ValidateAddBindingParameters和 ApplyDispatchBehavior。 该方法 Validate 用于确保行为可以应用于服务。 在本示例中,此实现确保不使用 Single 配置服务。 该方法 AddBindingParameters 用于配置服务的绑定。 在此方案中不需要它。 ApplyDispatchBehavior 用于配置服务的调度程序。 WCF 在 ServiceHost 初始化时调用此方法。 以下参数将传递到此方法中:
Description:此参数为整个服务提供服务说明。 这可用于检查有关服务终结点、协定、绑定和其他数据的说明数据。ServiceHostBase:此参数提供当前正在初始化的ServiceHostBase。
在自定义 IServiceBehavior 实现中,会实例化一个新的 ObjectPoolingInstanceProvider 实例,并将该实例分配到 ServiceHostBase 的每个 InstanceProvider 的 DispatchRuntime 属性。
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
// Create an instance of the ObjectPoolInstanceProvider.
ObjectPoolingInstanceProvider instanceProvider = new
ObjectPoolingInstanceProvider(description.ServiceType,
minPoolSize);
// Forward the call if we created a ServiceThrottlingBehavior.
if (this.throttlingBehavior != null)
{
((IServiceBehavior)this.throttlingBehavior).ApplyDispatchBehavior(description, serviceHostBase);
}
// In case there was already a ServiceThrottlingBehavior
// (this.throttlingBehavior==null), it should have initialized
// a single ServiceThrottle on all ChannelDispatchers.
// As we loop through the ChannelDispatchers, we verify that
// and modify the ServiceThrottle to guard MaxPoolSize.
ServiceThrottle throttle = null;
foreach (ChannelDispatcherBase cdb in
serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
// Make sure there is exactly one throttle used by all
// endpoints. If there were others, we could not enforce
// MaxPoolSize.
if ((this.throttlingBehavior == null) &&
(this.maxPoolSize != Int32.MaxValue))
{
throttle ??= cd.ServiceThrottle;
if (cd.ServiceThrottle == null)
{
throw new
InvalidOperationException(ResourceHelper.GetString("ExNullThrottle"));
}
if (throttle != cd.ServiceThrottle)
{
throw new InvalidOperationException(ResourceHelper.GetString("ExDifferentThrottle"));
}
}
foreach (EndpointDispatcher ed in cd.Endpoints)
{
// Assign it to DispatchBehavior in each endpoint.
ed.DispatchRuntime.InstanceProvider =
instanceProvider;
}
}
}
// Set the MaxConcurrentInstances to limit the number of items
// that will ever be requested from the pool.
if ((throttle != null) && (throttle.MaxConcurrentInstances >
this.maxPoolSize))
{
throttle.MaxConcurrentInstances = this.maxPoolSize;
}
}
除了IServiceBehavior实现外,ObjectPoolingAttribute类还有多个成员,可以通过属性参数来自定义对象池。 这些成员包括 MaxPoolSize, MinPoolSize 和 CreationTimeout,与 .NET Enterprise Services 提供的对象池功能集相匹配。
现在,可以通过使用新创建的自定义 ObjectPooling 属性对服务实现进行批注,将对象池行为添加到 WCF 服务。
[ObjectPooling(MaxPoolSize=1024, MinPoolSize=10, CreationTimeout=30000)]
public class PoolService : IPoolService
{
// …
}
运行示例
此示例演示在某些方案中使用对象池可以获得的性能优势。
服务应用程序实现两个服务 -- WorkService 和 ObjectPooledWorkService。 这两个服务共享相同的实现 -- 它们都需要昂贵的初始化,然后公开一个 DoWork() 相对便宜的方法。 唯一的区别是 ObjectPooledWorkService 配置了对象池:
[ObjectPooling(MinPoolSize = 0, MaxPoolSize = 5)]
public class ObjectPooledWorkService : IDoWork
{
public ObjectPooledWorkService()
{
Thread.Sleep(5000);
ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService instance created.");
}
public void DoWork()
{
ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService.GetData() completed.");
}
}
运行客户端时,它会调用 WorkService 5 次。 然后,它会调用 ObjectPooledWorkService 5 次。 然后显示时间差异:
Press <ENTER> to start the client.
Calling WorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling WorkService took: 26722 ms.
Calling ObjectPooledWorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling ObjectPooledWorkService took: 5323 ms.
Press <ENTER> to exit.
注释
首次运行客户端时,这两个服务似乎需要大约相同的时间。 如果重新运行示例,则可以看到返回 ObjectPooledWorkService 速度要快得多,因为该对象的实例已存在于池中。
设置、生成和运行示例
确保已为 Windows Communication Foundation 示例 执行One-Time 安装过程。
要生成解决方案,请按照生成 Windows Communication Foundation 示例中的说明进行操作。
若要在单台计算机或跨计算机配置中运行示例,请按照 运行 Windows Communication Foundation 示例中的说明进行操作。
注释
如果使用 Svcutil.exe 重新生成此示例的配置,请确保修改客户端配置中的终结点名称以匹配客户端代码。