阅读464 返回首页    go 阿里云 go 技术社区[云栖]


[原创]WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响

在本系列的上一篇文章中,我们重点讨论了线程关联性对service和callback的操作执行的影响:在service host的时候,可以设置当前线程的SynchronizationContext,那么在默认情况下,service操作的执行将在该SynchronizationContext下执行(也就将service操作包装成delegate传入SynchronizationContext的Send或者Post方法);同理,对于Duplex同行方式来讲,在client调用service之前,如果设置了当前线程的SynchronizationContext,callback操作也将自动在该SynchronizationContext下执行。

详细讲,由于WindowsFormsSynchronizationContext的Post或者Send方法,会将目标方法的执行传到UI主线程,所以可以说,所有的service操作都在同一个线程下执行,如果有多个client的请求同时抵达,他们并不能像我们希望的那样并发的执行,而只能逐个以串行的方式执行。(Source Code从这里下载)

一、通过实例证明线程关联性对并发的影响

image

   1: namespace Artech.ThreadAffinity2.Contracts
   2: {
   3:     [ServiceContract]
   4:     public interface IService
   5:     {
   6:         [OperationContract]
   7:         void DoSomething();
   8:     }
   9: }
2、Service:Service

   1: namespace Artech.ThreadAffinity2.Services
   2: {
   3:     public class Service:IService
   4:     {
   5:         public static ListBox DispalyPanel
   6:         { get; set; } 
   7:  
   8:         public static SynchronizationContext SynchronizationContext
   9:         { get; set; } 
  10:  
  11:         #region IService Members 
  12:  
  13:         public void DoSomething()
  14:         {
  15:             Thread.Sleep(5000);
  16:             int threadID = Thread.CurrentThread.ManagedThreadId;
  17:             DateTime endTime = DateTime.Now;
  18:             SynchronizationContext.Post(delegate
  19:             {
  20:                 DispalyPanel.Items.Add(string.Format("Serice execution ended at {0}, Thread ID: {1}",
  21:                     endTime, threadID));
  22:             }, null);
  23:         } 
  24:  
  25:         #endregion
  26:     }
  27: } 

   1: namespace Artech.ThreadAffinity2.Hosting
   2: {
   3:     public partial class HostForm : Form
   4:     {
   5:         private ServiceHost _serviceHost; 
   6:  
   7:         public HostForm()
   8:         {
   9:             InitializeComponent();
  10:         } 
  11:  
  12:         private void HostForm_Load(object sender, EventArgs e)
  13:         {
  14:             this.listBoxResult.Items.Add(string.Format("The ID of the Main Thread: {0}", Thread.CurrentThread.ManagedThreadId));
  15:             this._serviceHost = new ServiceHost(typeof(Service));
  16:             this._serviceHost.Opened += delegate
  17:             { 
  18:                 this.Text = "Service has been started up!";
  19:             };
  20:             Service.DispalyPanel = this.listBoxResult;
  21:             Service.SynchronizationContext = SynchronizationContext.Current;
  22:             this._serviceHost.Open();
  23:         } 
  24:  
  25:         private void HostForm_FormClosed(object sender, FormClosedEventArgs e)
  26:         {
  27:             this._serviceHost.Close();
  28:         }
  29:     }
  30: } 
  31:  

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <services>
   5:             <service name="Artech.ThreadAffinity2.Services.Service">
   6:                 <endpoint binding="basicHttpBinding" contract="Artech.ThreadAffinity2.Contracts.IService" />
   7:                 <host>
   8:                     <baseAddresses>
   9:                         <add baseAddress="https://127.0.0.1/service" />
  10:                     </baseAddresses>
  11:                 </host>
  12:             </service>
  13:         </services>
  14:     </system.serviceModel>
  15: </configuration> 

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <client>
   5:             <endpoint address="https://127.0.0.1/service" binding="basicHttpBinding"
   6:                 contract="Artech.ThreadAffinity2.Contracts.IService" name="service" />
   7:         </client>
   8:     </system.serviceModel>
   9: </configuration>
   1: namespace Clients
   2: {
   3:     class Program
   4:     {
   5:         static void Main(string[] args)
   6:         {
   7:             using (ChannelFactory<IService> channelFactory = new ChannelFactory<IService>("service"))
   8:             {
   9:                 IList<IService> channelList = new List<IService>();
  10:                 for (int i = 0; i < 10; i++)
  11:                 {
  12:                     channelList.Add(channelFactory.CreateChannel());
  13:                 } 
  14:  
  15:                 Array.ForEach<IService>(channelList.ToArray<IService>(), 
  16:                     delegate(IService channel)
  17:                 { 
  18:                     ThreadPool.QueueUserWorkItem(
  19:                     delegate
  20:                     {
  21:                         channel.DoSomething();
  22:                         Console.WriteLine("Service invocation ended at {0}", DateTime.Now);
  23:                     }, null);
  24:                 } );
  25:                 Console.Read();
  26:             }
  27:         }
  28:     }
  29: } 
  30:  

image

image

二、解除线程的关联性

在本系列的上一篇文章,我们介绍了service的线程关联性通过ServiceBeahavior的控制。UseSynchronizationContext实际上代表的是是否使用预设的SynchronizationContext(实际上是DispatchRuntime的SynchronizationContext属性中制定的)。我们对service的代码进行如下简单的修改,使service执行过程中不再使用预设的SynchronizationContext。

   1: namespace Artech.ThreadAffinity2.Services
   2: {
   3:     [ServiceBehavior(UseSynchronizationContext = false)]    
   4:     public class Service:IService
   5:     {
   6:  
   7:          //...
   8:     }
   9: }

image

image

结论:当我们使用Windows Form Application进行service host的时候,首先应该考虑到在默认的情况下具有线程关联特性。你需要评估的service的整个操作是否真的需要依赖于当前UI线程,如果不需要或者只有部分操作需要,将UseSynchronizationContext 设成false,将会提高service处理的并发量。对于依赖于当前UI线程的部分操作,可以通过SynchronizationContext实现将操作Marshal到UI线程中处理,对于这种操作,应该尽力那个缩短执行的时间。

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后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)
WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响
WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇]
WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇]
WCF后续之旅(14):TCP端口共享
WCF后续之旅(15): 逻辑地址和物理地址
WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter)
WCF后续之旅(17):通过tcpTracer进行消息的路由


作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及着作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文链接

最后更新:2017-10-30 17:04:09

  上一篇:go  在TensorFlow中对比两大生成模型:VAE与GAN(附测试代码)
  下一篇:go  《WCF后续之旅》博文系列总结[共17篇]