究竟应该怎么调用WCF服务?

简介: 对于下面几种方式,哪一种是最合适的,我期望的是这样的: 1) 在客户端,对于TCP确保使用连接池,在每一次使用连接之后归还连接等待复用。由于连接池是宝贵的资源,不可能每一次调用都去创建,而是只创建一次。

对于下面几种方式,哪一种是最合适的,我期望的是这样的:

1) 在客户端,对于TCP确保使用连接池,在每一次使用连接之后归还连接等待复用。由于连接池是宝贵的资源,不可能每一次调用都去创建,而是只创建一次。

2) 在服务端,调用完毕之后可以尽快释放服务实例(设置的是  [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)])

3) 对于多线程环境,是线程安全的并且也是高性能的,可以利用多线程环境提高速度的。

我想了一下有这些写法,每一种测试分别用TCP和HTTP信道运行一遍:

我分别测试了:

1) 用ClientBase的方式(也就是添加服务引用后生成的ServiceClient的方式)

2) 用ChannelFactory方式(直接复用服务契约的方式)

3) 是否主动开关的区别(主动开关是否会影响性能?是否必要?是否会影响连接的复用?)

4) 是否共享的区别(共享后是否有性能提高?是否线程安全?)

Parallel.For(0, it, (i, loopState) =>
{
     using (ServiceClient client = new ServiceClient(s))
     {
         try
         {

             if (client.Add(1, 2) != 3) throw new Exception("error");
         }
         catch (Exception ex)
         {
             Console.WriteLine(ex.ToString());
             loopState.Break();
         }

     }

});

Console.WriteLine(s + " 每次创建 Client:" + sw.ElapsedMilliseconds + "," + it * 1000 / sw.ElapsedMilliseconds);

2)

Parallel.For(0, it, (i, loopState) =>
{
     using (ServiceClient client = new ServiceClient(s))
     {
         client.Open();
         try
         {

             if (client.Add(1, 2) != 3) throw new Exception("error");
         }
         catch (Exception ex)
         {
             Console.WriteLine(ex.ToString());
             loopState.Break();
         }
         client.Close();
     }

});

Console.WriteLine(s + " 每次创建 Client (主动开关):" + sw.ElapsedMilliseconds + "," + it * 1000 / sw.ElapsedMilliseconds);

3)

using (ServiceClient client = new ServiceClient(s))
{
     client.Open();
     Parallel.For(0, it, (i, loopState) =>
     {

         try
         {
             if (client.Add(1, 2) != 3) throw new Exception("error");
         }
         catch (Exception ex)
         {
             Console.WriteLine(ex.ToString());
             loopState.Break();
         }
     });
     client.Close();
}
Console.WriteLine(s + " 共享 Client:" + sw.ElapsedMilliseconds + "," + it * 1000 / sw.ElapsedMilliseconds);

4)

using (ServiceClient client = new ServiceClient(s))
{
     client.Open();
     Parallel.For(0, it, (i, loopState) =>
     {

         try
         {
             if (client.Add(1, 2) != 3) throw new Exception("error");
         }
         catch (Exception ex)
         {
             Console.WriteLine(ex.ToString());
             loopState.Break();
         }
     });

     client.Close();
}

Console.WriteLine(s + " 共享 Client (主动开关):" + sw.ElapsedMilliseconds + "," + it * 1000 / sw.ElapsedMilliseconds);

5)

Parallel.For(0, it, (i, loopState) =>
  {
      using (ChannelFactory<IGeneralCalculator> cf = new ChannelFactory<IGeneralCalculator>(s))
      {
          try
          {

              var client = cf.CreateChannel();

              if (client.Add(1, 2) != 3) throw new Exception("error");
              (client as ICommunicationObject).Close();
          }
          catch (Exception ex)
          {
              Console.WriteLine(ex.ToString());
              loopState.Break();
          }
      }

  });

Console.WriteLine(s + " 每次创建 ChannelFactory:" + sw.ElapsedMilliseconds + "," + it * 1000 / sw.ElapsedMilliseconds);

6)

Parallel.For(0, it, (i, loopState) =>
{
     using (ChannelFactory<IGeneralCalculator> cf = new ChannelFactory<IGeneralCalculator>(s))
     {
         cf.Open();
         try
         {

             var client = cf.CreateChannel();


             if (client.Add(1, 2) != 3) throw new Exception("error");
             (client as ICommunicationObject).Close();
         }
         catch (Exception ex)
         {
             Console.WriteLine(ex.ToString());
             loopState.Break();
         }

         cf.Close();
     }
    
});

Console.WriteLine(s + " 每次创建 ChannelFactory (主动开关):" + sw.ElapsedMilliseconds + "," + it * 1000 / sw.ElapsedMilliseconds);


7)


using (ChannelFactory<IGeneralCalculator> cf = new ChannelFactory<IGeneralCalculator>(s))
{
     Parallel.For(0, it, (i, loopState) =>
     {
         try
         {

             var client = cf.CreateChannel();

             if (client.Add(1, 2) != 3) throw new Exception("error");
             (client as ICommunicationObject).Close();
         }
         catch (Exception ex)
         {
             Console.WriteLine(ex.ToString());
             loopState.Break();
         }
     });

}

Console.WriteLine(s + " 共享 ChannelFactory:" + sw.ElapsedMilliseconds + "," + it * 1000 / sw.ElapsedMilliseconds);


8)


using (ChannelFactory<IGeneralCalculator> cf = new ChannelFactory<IGeneralCalculator>(s))
{
     cf.Open();
     Parallel.For(0, it, (i, loopState) =>
     {
         try
         {

             var client = cf.CreateChannel();

             if (client.Add(1, 2) != 3) throw new Exception("error");
             (client as ICommunicationObject).Close();
         }
         catch (Exception ex)
         {
             Console.WriteLine(ex.ToString());
             loopState.Break();
         }
     });
     cf.Close();
}

Console.WriteLine(s + " 共享 ChannelFactory (主动开关):" + sw.ElapsedMilliseconds + "," + it * 1000 / sw.ElapsedMilliseconds);

测试结果如下(Y坐标是每秒操作执行成功数):

image

1) HTTP总体性能比TCP差不少,但是不管哪种方式都差不多,我猜测差不多的原因是因为HTTP是短连接而TCP是长连接的关系。

2) 按我的想法,每次创建ChannelFactory性能是最差的,可能是每一次都在使用新的连接池,事实也是这样,但是也没有差太多

3) 我本来以为共享ChannelFactory和每次创建Client的方式性能差不多的(因为ClientBase会缓存ChannelFactory),但是差距还是挺大的

4) 共享Client方式能有这么高的性能让人意外,我本来以为会和每次创建Client一样,不知这样用会不会有什么问题

官方的文档中虽然是每次创建Client或ChannelFactory后及时关闭的,但他那个都是控制台程序其实怎么做都不重要,对于ASP.NET多线程环境中的使用究竟哪种最靠谱?

我个人觉得两种方式比较靠谱的(没看到官方有相关的最佳实践的说法,自己猜的):

1) 如果使用ClientBase(ServiceClient)方式的,每一次使用都创建,使用后及时关闭

2) 如果使用ChannelFactory方式的,共享方式使用,但是使用后及时关闭Create出来的Channel

(反正共享方式即使性能高我是不推荐的,可能会有很多潜在的问题)

反正不管怎么样,测试结论还是和自己想的不太一样,请大家讨论。

后记:

http://blogs.msdn.com/b/wenlong/archive/2007/10/26/best-practice-always-open-wcf-client-proxy-explicitly-when-it-is-shared.aspx

在此文中,作者的观点是如果希望共享,对于Client方式和ChannelFactory都要显式Open(),否则会在第一次调用后Open,那么一开始的时候可因为排队导致性能变差。

http://blogs.msdn.com/b/wenlong/archive/2007/10/27/performance-improvement-of-wcf-client-proxy-creation-and-best-practices.aspx

在此文中,作者的观点是:

1) 创建ChannelFactory(ClientBase内部也是创建ChannelFactory)是昂贵的,因为需要构建ContractDescription树、反射所有需要的CLR类型、构建信道栈,在.NET 3.5中进行了以下改进:A 在ClientBase内部缓存了ChannelFactory B 改善了信道管理的逻辑,因此不管是使用ClientBase还是直接使用ChannelFactory,性能都有改善

2) ClientBase对ChannelFactory的缓存采用的是MRU,大小是32。只要InstanceContext、endpointConfiguratioName以及remoteAddress参数匹配上就可以用到缓存。如果不想用到缓存,可以在Open ClientBase之前访问ChannelFactory、Endpoint或是ClientCredentials属性。

3) 最佳实践是 A 重用ServiceClient能获得最好的性能,但别忘记先Open B 每次都使用新的ServiceClient依靠缓存的话性能也不错 C 共享的话要注意多线程问题

http://blogs.msdn.com/b/wenlong/archive/2007/11/14/a-sample-for-wcf-client-proxy-pooling.aspx

作者在此文中给出了Client Proxy Pool的实现,他提到:

1) 通过客户的反馈我意识到直接重用代理不是理想的,因为可能多线程会出现竞争问题,以及关联的上下文可能会混乱。

2) 可能使用池的做法更合理,但是需要实现相关的同步、异常处理、无可用对象时新建、池大小可配置等逻辑。

作者给出了自己的实现,另外我也找到了基于池的一个更完善的动态代理的实现:http://wcfdynamicclient.codeplex.com/

作者: lovecindywang
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
11月前
|
前端开发
WCF更新服务引用报错的原因之一
WCF更新服务引用报错的原因之一
|
10月前
|
C# 数据安全/隐私保护
c#如何创建WCF服务到发布(SqlServer版已经验证)
c#如何创建WCF服务到发布(SqlServer版已经验证)
39 0
|
10月前
|
安全 数据库连接 数据库
WCF服务创建到发布(SqlServer版)
在本示例开始之前,让我们先来了解一下什么是wcf? wcf有哪些特点? wcf是一个面向服务编程的综合分层架构。该架构的项层为服务模型层。 使用户用最少的时间和精力建立自己的软件产品和外界通信的模型。它使得开发者能够建立一个跨平台的安全、可信赖、事务性的解决方案。且能与已有系统兼容写作。 简单概括就是:一组数据通信的应用程序开发接口。
66 0
|
11月前
Visual Studio 2022 创建 WCF服务 找不到
Visual Studio 2022 创建 WCF服务 找不到
|
C++
WCF基础教程(二)——解析iis8和iis8.5+VS2013发布wcf服务问题
WCF基础教程(二)——解析iis8和iis8.5+VS2013发布wcf服务问题
105 0
WCF基础教程(二)——解析iis8和iis8.5+VS2013发布wcf服务问题
WCF使用纯代码的方式进行服务寄宿
服务寄宿的目的是为了开启一个进程,为WCF服务提供一个运行的环境。通过为服务添加一个或者多个终结点,使之暴露给潜在的服务消费,服务消费者通过匹配的终结点对该服务进行调用,除去上面的两种寄宿方式,还可以以纯代码的方式实现服务的寄宿工作。
862 0
|
Windows
WCF服务寄宿到IIS
一.WCF简介: Windows Communication Foundation(WCF)是由微软开发的一系列支持数据通信的应用程序框架,可以翻译为Windows 通讯开发平台。整合了原有的windows通讯的 .net Remoting,WebService,Socket的机制,并融合有HTTP和FTP的相关技术。
1055 0
WCF服务自我寄宿
WCF服务的寄宿方式 WCF寄宿方式是一种非常灵活的操作,可以寄宿在各种进程之中,常见的寄宿有: IIS服务、Windows服务、Winform程序、控制台程序中进行寄宿,从而实现WCF服务的运行,为调用者方便、高效提供服务调用。
1002 0
|
网络架构
(纯代码)快速创建wcf rest 服务
因为有一个小工具需要和其它的业务对接数据,所以就试一下看能不能弄一个无需配置快速对接的方法出来,百(以)度(讹)过(传)后(讹),最后还是对照wcf配置对象调试出来了: 1.创建WebHttpBinding 2.
982 0