WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制

原文:WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制

和传统的分布式远程调用一样,WCF的服务调用借助于服务代理(Service Proxy)。而ChannelFactory<T>则是服务代理的创建者。WCF采用基于终结点(Endpoint)服务消费方式:WCF服务通过一个或者多个终结点暴露给潜在的服务消费者,服务的消费中通过与之匹配的终结点与之交互。在客户端,我们具有两种典型的服务代理创建方式,其一是通过诸如SvcUtil.exe这样的工具导入服务的元数据生成相应的服务代理(一个继承自ClientBase<T>的类型)代码和相关配置;其二是直接通过相应的终结点信息(通过代码指定或者配置)创建ChannelFactory<T>对象,并借助该对象直接进行服务代理的创建。

实际上,即使通过ClientBase<T>对象进行服务调用,其内部也是调用ChannelFactory<T>创建的服务代理。整个ChannelFactory<T>的创建是一项相对复杂并且费时的工作,会涉及很多诸如反射、配置文件的读取等操作。为了提高服务调用的性能,在.NET 3.5中,WCF在ClientBase<T>中引入了ChannelFactory<T>的缓存机制。

一、如何实现对ChannelFactory<T>的缓存

为了让读者对ChannelFactory<T>的缓存机制有一个直观的认识,我们来做一个简单的实验:在一个Console应用中执行如下的代码,其中CalculatorClient可以看成是本节开篇时自定义的服务代理类。在本例中,先后以相同的方式(调用相同的构造函数,传入相同的参数)创建并开启了两个CalculatorClient对象,然后检验它们的ChannelFactory是否是相同的对象。

   1: CalculatorClient proxy1 = new CalculatorClient("calculateservice");
   2: proxy1.Open();

   3: CalculatorClient proxy2 = new CalculatorClient("calculateservice");

   4: proxy2.Open();Console.WriteLine("object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = {0}", 

   5: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory));

输出结果:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = True

从输出的结果,可以看出两个不同的ClientBase<T>对象使用了相同的ChannelFactory<T>对象。这得益于在.NET 3.5中新加入的ChannelFactory<T>的缓存机制。那么,在WCF客户端框架内部对ChannelFactory<T>的缓存是如何实现的呢?

实际上,ChannelFactory<T>的缓存实现很简单,被创建出来的ChannelFactory<T>集合通过ClientBase<T>的一个静态变量保存起来。我们可以将这个ChannelFactory<T>集合看成是一个字典,字典的值就是ChannelFactory<T>,而键则通过下面三个对象派生:

  • CallbackInstance:以InstanceContext对象表示的对回调对象的封装;
  • EndpointConfigurationName:终结点在配制文件中的名称;
  • RemoteAddress:终结点的远程地址,类型为EndpointAddress。

它们分别与ClienBase<T>构造函数中相应的参数相匹配。当调用某个构造函数创建对象的时候,WCF将传入的三个参数作为Key(如果再构造函数中并未指定相应的参数,会使用默认值,EndpointConfigurationName、CallbackInstance和RemoteAddress的默认值分别为*、null和null),从缓存(静态变量)中去找匹配的ChannelFactory<T>对象,如果成功找到,则直接返回,否则重新创建,在返回之前将其放入缓存中。

从这个意义上讲,多个ClienBase<T>对象能够重用相同的ChannelFactory<T>对象的前提是它们使用相同的构造函数,并传入相同的参数被创建。为了验证这一点,再来做一个实验,只须要将上面的例子稍加修改,通过另一个重载构造函数来创建CalculatorClient对象。

   1: CalculatorClient proxy1 = new CalculatorClient("calculateservice",new EndpointAddress("http://127.0.0.1:9999/calculateservice");
   2: proxy1.Open();

   3:  

   4: CalculatorClient proxy2 = new CalculatorClient("calculateservice");

   5: proxy2.Open();

   6: Console.WriteLine("object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = {0}", 

   7: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory));

   8:  

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

输出结果:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = False

实际上,proxy1和proxy2最终使用的终结点地址是相同的(http://127.0.0.1:9999/ calculatorservice),只不过一个是通过代码指定的,另一个则是通过配置文件配置的。但是,就是因为创建ClienBase<T>时使用了不同的构造函数重载,导致不能重用同一个ChannelFactory<T>对象。

ChannelFactory<T>的重用避免了频繁地常见ChannelFactory<T>对象,从而获得更好的性能。在具体的应用中,我们应该尽可能地利用这样的机制。但是,由于编程人员对ChannelFactory<T>的缓存机制不了解,不知不觉就会使这个缓存机制失效。接下来就来讨论这个问题。

二、ChannelFactory<T>缓存机制的失效

总体来讲,下面的两种情况会引起ChannelFactory<T>缓存机制失效。

  • 在构造函数中传入绑定对象构建ClientBase<T>;
  • 在ClientBase<T>开启(调用Open方法)之前,访问如下三个只读属性:ChannelFactory、Endpoint和ClientCredential。

为了加深读者的理解,我们通过实验的方式来证实上面的两种说法。为了验证在构造函数中传入绑定对象对ChannelFactory<T>缓存机制的影响,写了如下的代码:通过Binding和EndpointAddress对象创建ClienBase<T>对象。

   1: Binding binding = new BasicHttpBinding();
   2: EndpointAddress address = new EndpointAddress("http://127.0.0.1:9999/calculateservice");

   3: CalculatorClient proxy1 = new CalculatorClient(binding,address);

   4: proxy1.Open();

   5: CalculatorClient proxy2 = new CalculatorClient(binding, address);

   6: proxy2.Open();

   7: Console.WriteLine("object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = {0}", 

   8: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory));

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

输出结果:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = False

接下来,再通过实验整个在ClientBase<T>开启(调用Open方法)之前访问ChannelFactory、Endpoint和ClientCredential三个只读属性对ChannelFactory<T>缓存机制的影响。在这里,以访问ChannelFactory属性为例

   1: CalculatorClient proxy1 = new CalculatorClient("calculateservice");
   2: ChannelFactory<ICalculator> factory = proxy1.ChannelFactory;

   3: proxy1.Open();

   4: CalculatorClient proxy2 = new CalculatorClient("calculateservice");

   5: proxy2.Open();

   6: Console.WriteLine("object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = {0}", 

   7: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory));

   8:  

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

输出结果:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1: object.ReferenceEquals(proxy1.ChannelFactory, proxy2.ChannelFactory) = False
时间: 2024-12-11 10:12:30

WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制的相关文章

《WCF技术剖析》博文系列汇总[持续更新中]

http://www.cnblogs.com/artech/archive/2009/11/21/1607686.html 近半年以来,一直忙于我的第一本WCF专著<WCF技术剖析(卷1)>的写作,一直无暇管理自己的Blog.在<WCF技术剖析(卷1)>写作期间,对WCF又有了新的感悟,为此以书名开始本人的第三个WCF系列.本系列的目的在于对<WCF技术剖析>的补充,会对书中的一些内容进行展开讲述,同时会囊括很多由于篇幅的原因忍痛割弃的内容. [第1篇] 通过一个ASP

WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]

原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇] 在<上篇>中,我通过使用Delegate的方式解决了服务调用过程中的异常处理以及对服务代理的关闭.对于<WCF技术剖析(卷1)>的读者,应该会知道在第7章中我通过类似于AOP的方式解决了相似的问题,现在我们来讨论这个解决方案. 通过<服务代理不能得到及时关闭会有什么后果?>的介绍,我们知道了及时关闭服务代理的重要意义,并且给出了正确的编程方式.如果严格按照上面的编程方式,就意味着对于每一个服务调用,都要

WCF技术剖析之十一:异步操作在WCF中的应用(上篇)

原文:WCF技术剖析之十一:异步操作在WCF中的应用(上篇) 按照操作执行所需的资源类型,我们可以将操作分为CPU绑定型(CPU Bound)操作和I/O绑定型(I/O Bound)操作.对于前者,操作的执行主要利用CPU进行密集的计算,而对于后者,大部分的操作处理时间花在I/O操作处理,比如访问数据库.文件系统.网络资源等.对于I/O绑定型操作,我们可以充分利用多线程的机制,让多个操作在自己的线程并发执行,从而提高系统性能和响应能力.服务调用就是典型的I/O绑定型操作,所以多线程在服务调用中具

WCF技术剖析之十一:异步操作在WCF中的应用(下篇)

原文:WCF技术剖析之十一:异步操作在WCF中的应用(下篇) 说完了客户端的异步服务调用(参阅WCF技术剖析之十一:异步操作在WCF中的应用(上篇)),我们在来谈谈服务端如何通过异步的方式为服务提供实现.在定义服务契约的时候,相信大家已经注意到了OperationContractAttribute特性具有一个bool类型的AsynPattern.该属性可以将一个服务操作定义成异步实现模式,接下来的内容主要是着眼于介绍异步操作的定义和实现原理. 一.异步操作的定义和实现原理 实现WCF异步服务操作

WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

原文:WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理 在前面一片文章(服务代理不能得到及时关闭会有什么后果?)中,我们谈到及时关闭服务代理(Service Proxy)在一个高并发环境下的重要意义,并阐明了其根本原因.但是,是否直接调用ICommunicationObject的Close方法将服务代理关闭就万事大吉了呢?事情远不会这么简单,这其中还会涉及关于异常处理的一些操作,这就是本篇文章需要讨论的话题. 一.异常的抛出与Close的失败 一般情况下,当服务端抛出异常,客户客户

WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化

在本篇文章中,我们将讨论WCF四大契约(服务契约.数据契约.消息契约和错误契约)之一的消息契约(Message Contract).服务契约关注于对服务操作的描述,数据契约关注于对于数据结构和格式的描述,而消息契约关注的是类型成员与消息元素的匹配关系. 我们知道只有可序列化的对象才能通过服务调用在客户端和服务端之间进行传递.到目前为止,我们知道的可序列化类型有两种:一种是应用了System.SerializableAttribute特性或者实现了System.Runtime.Serializat

WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]

原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇] 在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道:当遇到某些异常,我们需要强行中止(Abort)信道,相关的原理,可以参考我的文章<服务代理不能得到及时关闭会有什么后果?>.在真正的企业级开发中,正如我们一般不会让开发人员手工控制数据库连接的开启和关闭一样,我们一般也不会让开发人员手工去创建.开启.中止和关闭信道,这些工作是框架应该完成的操作.这篇文章,我们就来介绍如果通过一些编程技巧,

WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构

原文:WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构 细算起来,已经有好几个月没有真正的写过文章了.近半年以来,一直忙于我的第一本WCF专著<WCF技术剖析>的写作,一直无暇管理自己的Blog.到目前为止<WCF技术剖析(卷1)>的写作暂告一段落,初步预计于下个月由武汉博文视点出版.在<WCF技术剖析>写作期间,对WCF又有了新的感悟,为此以书名开始本人的第三个WCF系列.本系列的目的在于对<WCF技术剖析>的补充,会对书中的一些内容进行展

WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]

原文:WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载] 我们有两种典型的WCF调用方式:通过SvcUtil.exe(或者添加Web引用)导入发布的服务元数据生成服务代理相关的代码和配置:通过ChannelFactory<TChannel>创建服务代理对象.在这篇文章中,我们采用一种独特的方式进行服务的调用.从本质上讲,我们只要能够创建于服务端相匹配的终结点,就能够实现正常的服务调用.在WCF客户端元数据架构体系中,利用MetadataExchangeClient可以获取服