前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[原创]WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响

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

作者头像
蒋金楠
发布2022-05-09 11:44:29
2180
发布2022-05-09 11:44:29
举报
文章被收录于专栏:大内老A大内老A

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

对于Windows Form Application来讲,由于UI Control的操作执行只能在control被创建的线程中被操作,所以一这样的方式实现了自己的SynchronizationContext(WindowsFormsSynchronizationContext):将所有的操作Marshal到UI线程中。正因为如此,当我们通过Windows Form Application进行WCF service的host的时候,将会对service的并发执行带来非常大的影响。

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

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

我们可以通过一个简单的例子证明:在默认的情况下,当我们通过Windows Form Application进行service host的时候,service的操作都是在同一个线程中执行的。我们照例创建如下的四层结构的WCF service应用:

image
image

1、Contract:IService

代码语言:javascript
复制
1: namespace Artech.ThreadAffinity2.Contracts
       2: {
       3:     [ServiceContract]
       4:     public interface IService
       5:     {
       6:         [OperationContract]
       7:         void DoSomething();
       8:     }
       9: }

2、Service:Service

代码语言:javascript
复制
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: }

为了演示对并发操作的影响,在DoSomething()中,我将线程休眠10s以模拟一个相对长时间的操作执行;为了能够直观地显示操作执行的线程和执行完成的时间,我将他们都打印在host该service的Windows Form的ListBox中,该ListBox通过static property的方式在host的时候指定。并将对ListBox的操作通过UI线程的SynchronizationContext(也是通过static property的方式在host的时候指定)的Post中执行(实际上,在默认的配置下,不需要如此,因为service操作的执行始终在Host service的UI线程下)。

3、Hosting

我们将service 的host放在一个Windows Form Application的某个一个Form的Load事件中。该Form仅仅具有一个ListBox:

代码语言:javascript
复制
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:

在HostForm_Load,先在ListBox中显示当前线程的ID,然后通过Service.DispalyPanel和Service.SynchronizationContext 为service的执行设置LisBox和SynchronizationContext ,最后将servicehost打开。下面是Configuration:

代码语言:javascript
复制
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="http://127.0.0.1/service" />
      10:                     </baseAddresses>
      11:                 </host>
      12:             </service>
      13:         </services>
      14:     </system.serviceModel>
      15: </configuration>

4、Client

我们通过一个Console Application来模拟client端程序,先看看configuration:

代码语言:javascript
复制
1: <?xml version="1.0" encoding="utf-8" ?>
       2: <configuration>
       3:     <system.serviceModel>
       4:         <client>
       5:             <endpoint address="http://127.0.0.1/service" binding="basicHttpBinding"
       6:                 contract="Artech.ThreadAffinity2.Contracts.IService" name="service" />
       7:         </client>
       8:     </system.serviceModel>
       9: </configuration>

下面是service调用的代码:

代码语言:javascript
复制
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:

首先通过ChannelFactory<IService> 先后创建了10个Proxy对象,然后以异步的方式进行service的调用(为了简单起见,直接通过ThreadPool实现异步调用),到service调用结束将当前时间输出来。我们来运行一下我们的程序,看看会出现怎样的现象。先来看看service端的输出结果:

image
image

通过上面的结果,从执行的时间来看service执行的并非并发,而是串行;从输出的线程ID更能说明这一点:所有的操作的执行都在同一个线程中,并且service执行的线程就是host service的UI线程。这充分证明了service的执行具有与service host的线程关联性。通过Server端的执行情况下,我们不难想象client端的执行情况。虽然我们是以异步的方式进行了10次service调用,但是由于service的执行并非并发执行,client的执行结果和同步下执行的情况并无二致:

image
image

二、解除线程的关联性

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

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

再次运行我们的程序,看看现在具有怎样的表现。首先看server端的输出结果:

image
image

我们可以看出,service的执行并不在service host的主线程下,因为Thread ID不一样,从时间上看,也可以看出它们是并发执行的。从Client的结果也可以证明这一点:

image
image

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2008-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、通过实例证明线程关联性对并发的影响
  • 二、解除线程的关联性
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档