`
varsoft
  • 浏览: 2429260 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

[原创]WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance

阅读更多

我们知道WCF有3种典型的对service instance进行实例化的方式,他们分别与WCF的三种InstanceContextMode相匹配,他们分别是PerCall,PerSession和Single。PerCall为每次service invocation创建一个新的service instance; 而PerSession则让一个service instance处理来自通过各Session(一般是同一个proxy对象)的调用请求;而Single则是用同一个service instance来处理所有的调用请求。SOA的一个原则是创建无状态的service(stateless service),PerCall应该是我们经常使用的实例化方式,尽管PerSession是默认的InstanceContextMode。

但是对于PerCall这种实例化方式来说,为每次service请求都创建新的service instance,有时候显得有点极端,频繁的对象创建会对系统的性能造成一定的影响。我们能够以池的机制(Pooling)进行对象的获取和创建呢:当service调用请求抵达service端,先试图从池中获取一个没有被使用的service instance,如何找到,直接获取该对象;否则创建新的对象。当service instance对象执行完毕,将对象释放到池中以供下次service 调用使用。

1、实现原理

我们今天就来试着实现这样的service instance提供机制。主要的实现原理是:让所有的service实现一个公共的interface(IPooledObject),定义了IsBusy的属性表明该对象当前是否正在被使用;为每个service type维护一个weak reference列表,每个weak reference对应一个确定的service instance,我们姑且将该weak reference列表成为该service type对应的对象池(object pool);为了处理service的调用需要提供一个确定的service instance的时候,遍历对象池,通过weak reference的Target属性找出一个可用的service instance(IsBusy=false)。如何顺利找到这样的service instance,则将其从对象池取出,将IsBusy属性设为true;如何没有找到,则通过反射创建一个新的service instance,将IsBusy设为true,同时利用weak reference将其包装,并将该weak reference加入到对象池中,最后返回该service instance用于处理service 调用。当service 调用结束,不是直接将其dispose掉,而是将其释放回对象池,供后续的service调用使用。

由于我们通过weak reference来实现对象池,weak reference引用的service instance是可以被GC回收的,这样做的好处是充分利用的GC的垃圾回收功能,避免不需要的service instance常驻内容,带来不必要的内存压力。此外,正是因为weak reference引用的service instance是可以被GC回收,我们需要一个后台的任务定期地将已经被回收的weak reference清除掉。

和本系列前两篇文章(WCF和Unity Appliation Block集成;WCF和Policy Injection Application Block集成)一样,涉及的是service instance的提供的问题,所以,我们也是通过自定义InstanceProvider来实现以对象池的机制创建service instance的目的。

2、PooledInstnaceProvider的创建

在创建我们自定义的InstanceProvider之前,我们先来介绍几个辅助的class:

I、IPooledObject

namespace Artech.WCFExtensions
{
public interface IPooledObject
{
bool IsBusy
{ get; set; }
}
}

由于我们要判断service instance是否可用,我们让所有的service type实现IPooledObject interface。IPooledObject 仅仅定义一个bool类型的属性:IsBusy。通过该属性判断service instance是否正在被使用。

II、WeakReferenceCollection和WeakReferenceDictionary

namespace Artech.WCFExtensions
{
public class WeakReferenceCollection:List<WeakReference>
{}

public class WeakReferenceDictionary : Dictionary<Type, WeakReferenceCollection>
{}
}

WeakReferenceCollection仅仅是WeakReference的列表,WeakReferenceDictionary 则是key为Type,value为WeakReferenceCollection的dictionary。在提供service instance的时候,就是根据service type为key找到对应的WeakReferenceCollection。

III、PooledInstanceLocator

namespace Artech.WCFExtensions
{
public static class PooledInstanceLocator
{
internal static WeakReferenceDictionary ServiceInstancePool
{ get; set; }

static PooledInstanceLocator()
{
ServiceInstancePool = new WeakReferenceDictionary();
}

public static IPooledObject GetInstanceFromPool(Type serviceType)
{
if(!serviceType.GetInterfaces().Contains(typeof(IPooledObject)))
{
throw new InvalidCastException("InstanceType must implement Artech.WCFExtensions.IPooledInstance");
}

if (!ServiceInstancePool.ContainsKey(serviceType))
{
ServiceInstancePool[serviceType] = new WeakReferenceCollection();
}

WeakReferenceCollection instanceReferenceList = ServiceInstancePool[serviceType] ;

lock (serviceType)
{
IPooledObject serviceInstance =null;
foreach (WeakReference weakReference in instanceReferenceList)
{
serviceInstance = weakReference.Target as IPooledObject;
if (serviceInstance != null && !serviceInstance.IsBusy)
{
serviceInstance.IsBusy = true;
return serviceInstance;
}
}

serviceInstance = Activator.CreateInstance(serviceType) as IPooledObject;
serviceInstance.IsBusy = true;
instanceReferenceList.Add(new WeakReference(serviceInstance));
return serviceInstance;
}
}

public static void Scavenge()
{
foreach (Type serviceType in ServiceInstancePool.Keys)
{
lock (serviceType)
{
WeakReferenceCollection instanceReferenceList = ServiceInstancePool[serviceType];
for (int i = instanceReferenceList.Count - 1; i > -1; i--)
{
if (instanceReferenceList[i].Target == null)
{
instanceReferenceList.RemoveAt(i);
}
}

}
}
}

public static void ReleaseInstanceToPool(IPooledObject instance)
{
instance.IsBusy = false;
}
}
}

PooledInstanceLocator实现了3基于对象池的功能:从对象池中获取对象(GetInstanceFromPool);将对象释放到池中(ReleaseInstanceToPool);清理被GC回收的weak reference(Scavenge)。代码很简单,我就不再一一介绍了。有一点需要注意的,由于PooledInstanceLocator工作在一个多线程环境下,保证线程的同步时最重要的。在本例中为了简便,我直接对service type对象进行枷锁,由于本例比较简单,不会引起什么问题。在实际的项目开发中,如何对Type对象进行加锁就需要三思了,因为type对象一个全局对象(可以参考的我的文章:What is type in managed heap),对其加锁很容易引起死锁。

IV、自定义InstanceProvider:PooledInstanceProvider

有了PooledInstanceLocator,我们的InstanceProvider就显得很简单了:

namespace Artech.WCFExtensions
{
class PooledInstanceProvider:IInstanceProvider
{
#region IInstanceProvider Members

public object GetInstance(InstanceContext instanceContext, Message message)
{
return PooledInstanceLocator.GetInstanceFromPool(instanceContext.Host.Description.ServiceType);
}

public object GetInstance(InstanceContext instanceContext)
{
return this.GetInstance(instanceContext, null);
}

public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
PooledInstanceLocator.ReleaseInstanceToPool(instance as IPooledObject);
}

#endregion
}
}

在GetInstance和ReleaseInstance,直接调用调用PooledInstanceLocator的GetInstanceFromPool和ReleaseInstanceToPool。

V、为自定义InstanceProvider定义Behavior

对于使用自定义InstanceProvider,我们一般可以通过Contract Behavior和Endpoint Behavior来实现,由于定义Behavior不是本篇文章的重点,在这里我仅仅通过Contract Behavior进行扩展这一种方式。

namespace Artech.WCFExtensions
{
public class PooledInstanceBehaviorAttribute:Attribute,IContractBehavior,IContractBehaviorAttribute
{

#region IContractBehavior Members

public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{}

public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{}

public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.InstanceProvider = new PooledInstanceProvider();
}

public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{}

#endregion

#region IContractBehaviorAttribute Members

public Type TargetContract
{
get { return null; }
}

#endregion
}
}

再ApplyDispatchBehavior中将DispatchRuntime 的InstanceProvider 设置成我们定义的PooledInstanceProvider就可以了。

3、将PooledInstanceProvider应用到WCF应用中

现在我们就创建一个简单的WCF应用将看看我们自定义的InstanceProvider能给我们带来什么。我们照例创建如下图一样的4层结构:

image

I、Contract:Contracts.IService

namespace Contracts
{
[ServiceContract]
[PooledInstanceBehavior]
public interface IService : IPooledObject
{
[OperationContract(IsOneWay =true)]
void DoSomething();
}
}

通过custom attribute的方式将PooledInstanceBehaviorAttribute应用到service conntract。Iservice继承我们定义的IPooledObject interface。

II、Service:Services.Service

namespace Services
{

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service:IService
{
static int count;

public Service()
{
Interlocked.Increment(ref count);
Console.WriteLine("{0}: Service instance is constructed!", count);
}

#region IService Members

public void DoSomething()
{}

#endregion

#region IPooledInstance Members

public bool IsBusy
{get;set;}

#endregion
}
}

我们应用PerCall InstanceContextMode。由于我们需要检测的是service instance的创建,所以我们通过下面的代码判断service instance创建的次数。

public Service()
{
Interlocked.Increment(ref count);
Console.WriteLine("{0}: Service instance is constructed!", count);
}

III、Hosting

namespace Hosting
{
class Program
{
static Timer ScavengingTimer;

static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(Service)))
{
host.Opened += delegate
{
Console.WriteLine("Service has been started up!");
};

host.Open();

ScavengingTimer = new Timer(delegate
{
PooledInstanceLocator.Scavenge();
}, null, 0, 5000);

Console.Read();

}
}
}
}

除了对service进行Host之外,Main()方法还通过一个Timer对象实现对对象池的清理工作(调用PooledInstanceLocator.Scavenge();),时间间隔是5s。
下面是configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Services.Service">
<endpoint binding="basicHttpBinding" contract="Contracts.IService" />
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1/service" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

IV、Client:Clients.

namespace Clients
{
class Program
{
static void Main(string[] args)
{
using (ChannelFactory<IService> channelFactory = new ChannelFactory<IService>("service"))
{
for (int i = 1; i <= 10; i++)
{
Console.WriteLine("{0}: invocate service!", i);
channelFactory.CreateChannel().DoSomething();
Thread.Sleep(1000);
}
}

Console.Read();
}
}
}

在上面的代码中,我们通过for循环进行了10次service调用。每次间隔1s.

我们看看运行的结果,这是client端的运行结果:

image

这是service端的结果:

image

可见service instance只创建了一次。因为方法执行太快,方法结束后service instance马上释放到对象池中,后续的调用一直使用的是同一个service instance。

然后我们把IService 的PooledInstanceBehavior注释掉。

namespace Contracts
{
[ServiceContract]
//[PooledInstanceBehavior]
public interface IService : IPooledObject
{
[OperationContract(IsOneWay =true)]
void DoSomething();
}
}

再次运行程序,service端将会得到下面的输出结果:

image

可见在没有运用PooledInstanceBehavior情况下,service instance的创建真正使“PerCall”。我们将PooledInstanceBehavior重新加上,然后通过在DoSomething方法中加上下面的代码延长该方法执行的时间:

namespace Services
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service:IService
{

#region IService Members

public void DoSomething()
{
Thread.Sleep(2000);
}

#endregion
}
}

再次运行程序,service端的运行结果如下图所示

image
由于我们将DoSomething方法的执行延长至2s,在这种情况下,由于client端的service掉用的间隔是1s,所有当第二次service调用抵达之后,第一次创建的service instance还没有被释放,所以需要重新创建新的service instance。当第三次service调用时,第一个service instance已经释放,以此类推,永远只需要两个service instance。这和上面的结果一致。

上面的运行结果都是在GC没有进行垃圾回收的情况下的运行结果,如何GC参与了又会有怎样的行为表现呢?在Hosting中,我们通过另一个Timer定期地进行垃圾回收(间隔为500ms):

namespace Hosting
{
class Program
{
static Timer ScavengingTimer;
static Timer GCTimer;
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(Service)))
{
host.Opened += delegate
{
Console.WriteLine("Service has been started up!");
};

host.Open();

ScavengingTimer = new Timer(delegate
{
PooledInstanceLocator.Scavenge();
}, null, 0, 5000);

GCTimer = new Timer(delegate
{
GC.Collect();
}, null, 0, 500);

Console.Read();

}
}
}
}

然后我们将serivice的DoSomething()操作执行时间缩短(比client调用service的间隔短:500ms),使得操作执行完毕后,还没有新的请求抵达,这样GC会将其垃圾回收。

namespace Services
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service:IService
{

#region IService Members

public void DoSomething()
{
Thread.Sleep(500);
}

#endregion
}
}

那么现在的输出结果将会是这样:

image

WCF后续之旅:
[原创]WCF后续之旅(1): WCF是如何通过Binding进行通信的
[原创]WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel
[原创]WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher
[原创]WCF后续之旅(4):WCF Extension Point 概览
[原创]WCF后续之旅(5): 通过WCF Extension实现Localization
[原创]WCF后续之旅(6): 通过WCF Extension实现Context信息的传递
[原创]WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成
[原创]WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成
[原创]WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I]
[原创]WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II]
[原创]WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance

我的WCF之旅:
[原创]我的WCF之旅(1):创建一个简单的WCF程序
[原创]我的WCF之旅(2):Endpoint Overview
[原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communication)
[原创]我的WCF之旅(4):WCF中的序列化(Serialization)- Part I
[原创]我的WCF之旅(4):WCF中的序列化(Serialization)- Part II
[原创]我的WCF之旅(5):Service Contract中的重载(Overloading)
[原创]我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
[原创]我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承
[原创]我的WCF之旅(8):WCF中的Session和Instancing Management
[原创]我的WCF之旅(9):如何在WCF中使用tcpTrace来进行Soap Trace
[原创]我的WCF之旅(10): 如何在WCF进行Exception Handling
[原创]我的WCF之旅(11):再谈WCF的双向通讯-基于Http的双向通讯 V.S. 基于TCP的双向通讯
[原创]我的WCF之旅(12):使用MSMQ进行Reliable Messaging
[原创]我的WCF之旅(13):创建基于MSMQ的Responsive Service

分享到:
评论

相关推荐

    我的WCF之旅源代码_创建一个简单的WCF程序

    我的WCF之旅源代码_创建一个简单的WCF程序

    WCF之旅:一个简单的WCF程序(vs2010源码)

    WCF之旅:一个简单的WCF程序(vs2010源码) 文章 + 源码 入门首选文章,折腾了好久才折腾出第一个wcf程序。 对准备学习wcf的人员绝对有意义

    我的WCF之旅:计算器 WCF样例源代码

    我的WCF之旅- 创建一个简单的WCF程序 - Artech WCF入门之选绝佳的例子 代源源于:《WCF全面解析 上》 编程工具:VS2010 语言:C# blog 《IIS站点中部署 WCF项目》

    Apress Pro WCF 4

    Apress Pro WCF 4 Practical Microsoft SOA Implementation, 2nd Part I: Introducing Windows Communication Foundation ■Chapter 1: WCF and SOA Basics ■Chapter 2: What’s New in WCF 4 ■Chapter 3: ...

    WCF 我的WCF之旅示例代码

    在学习WCF之旅的时候自己写得一些代码,不同的版本展示了逐渐深入的过程,有文字说明,很经典。

    我的WCF之旅源代码 IIS寄宿

    我的WCF之旅(1)配套代码,IIS寄宿 泛型 序列化

    我的WCF之旅后续篇

    我的WCF之旅后续篇,呀,要大于20字符啊,废话一下

    WCF之旅 WCF学习的相关资料

    WCF之旅 1. 创建一个简单的WCF程序 3.在WCF中实现双向通信(Bi-directional Communication) 5. 通过WCF Extension实现Localization ......

    WCFService可以通过web调用和WCF服务访问

    WCF服务,配置可同时支持web调用和WCF调用,可以用于C#程序服务访问,也支持java等程序访问。java通过web调用,C#可以通过wcf访问。

    wcfservice示例

    wcfservice示例wcfservice示例wcfservice示例wcfservice示例wcfservice示例

    WCF 学习之旅

    本课件包含课件,及对应源部分,为本人所做课件,可以对照文档一步步实现代码,本课件对WebService 进行了简单的介绍,WCF简单的应用 ,可以布置到各种寄宿平台上,

    编码实现创建WCF服务,创建客户端连接

    纯编码实现创建WCF服务,创建客户端连接,不需要配置文件,不需要添加服务引用。原始积分只需要5,CSDN自动修改所需积分,太恶心了,我会定期改回来。

    WCF实例 webservice

    由于WCF完全是由托管代码编写,因此开发WCF的应用程序与开发其它的.Net应用程序没有太大的区别,我们仍然可以像创建面向对象的应用程序那样,利用WCF来创建面向服务的应用程序。 2、互操作性 由于WCF最基本的...

    wcf服务window service

    wcf 服务寄宿到windows service上

    WCF实现聊天通信程序

    利用Wcf实现聊天程序,WCF很好的学习资料

    WCF开发实战

    WCF开发实战系列一:创建第一个WCF服务 WCF开发实战系列二:使用IIS发布WCF服务 WCF开发实战系列三:自运行WCF服务 WCF开发实战系列四:使用Windows服务发布WCF服务 WCF开发实战系列五:创建WCF客户端程序

    创建一个简单的WCF程序:WcfServices、WcfServices2

    该程序创建一个完整的WCF应用,应用功能虽然简单,但它涵盖了一个完整WCF应用的基本结构。对那些对WCF不是很了解的读者来说,这个例子将带领你正式进入WCF的世界。

    WCF Service编程源代码

    WCF Service 源代码 作者:Lowy WCF Service 源代码

    我的WCF之旅(合集)

    我把WCF之旅制作了个电子书,为WCF做点贡献吧

    Programming WCF Services (4th).pdf 2015 第4版 无水印

    ISBN-10: 1491944838 ISBN-13: 978-1491944837 Programming WCF Services is the authoritative, bestselling guide to Microsoft’s unified platform for developing modern, service-oriented applications on ...

Global site tag (gtag.js) - Google Analytics