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

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

阅读更多

在本系列的上一篇文章中,我们重点讨论了线程关联性对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

1、Contract:IService

namespace Artech.ThreadAffinity2.Contracts
{
[ServiceContract]
public interface IService
{
[OperationContract]
void DoSomething();
}
}

2、Service:Service

namespace Artech.ThreadAffinity2.Services
{
public class Service:IService
{
public static ListBox DispalyPanel
{ get; set; }

public static SynchronizationContext SynchronizationContext
{ get; set; }

#region IService Members

public void DoSomething()
{
Thread.Sleep(5000);
int threadID = Thread.CurrentThread.ManagedThreadId;
DateTime endTime = DateTime.Now;
SynchronizationContext.Post(delegate
{
DispalyPanel.Items.Add(string.Format("Serice execution ended at {0}, Thread ID: {1}",
endTime, threadID));
}, null);
}

#endregion
}
}

为了演示对并发操作的影响,在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:

namespace Artech.ThreadAffinity2.Hosting
{
public partial class HostForm : Form
{
private ServiceHost _serviceHost;

public HostForm()
{
InitializeComponent();
}

private void HostForm_Load(object sender, EventArgs e)
{
this.listBoxResult.Items.Add(string.Format("The ID of the Main Thread: {0}", Thread.CurrentThread.ManagedThreadId));
this._serviceHost = new ServiceHost(typeof(Service));
this._serviceHost.Opened += delegate
{
this.Text = "Service has been started up!";
};
Service.DispalyPanel = this.listBoxResult;
Service.SynchronizationContext = SynchronizationContext.Current;
this._serviceHost.Open();
}

private void HostForm_FormClosed(object sender, FormClosedEventArgs e)
{
this._serviceHost.Close();
}
}
}

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

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

4、Client

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

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://127.0.0.1/service" binding="basicHttpBinding"
contract="Artech.ThreadAffinity2.Contracts.IService" name="service" />
</client>
</system.serviceModel>
</configuration>

下面是service调用的代码:

namespace Clients
{
class Program
{
static void Main(string[] args)
{
using (ChannelFactory<IService> channelFactory = new ChannelFactory<IService>("service"))
{
IList<IService> channelList = new List<IService>();
for (int i = 0; i < 10; i++)
{
channelList.Add(channelFactory.CreateChannel());
}

Array.ForEach<IService>(channelList.ToArray<IService>(),
delegate(IService channel)
{
ThreadPool.QueueUserWorkItem(
delegate
{
channel.DoSomething();
Console.WriteLine("Service invocation ended at {0}", DateTime.Now);
}, null);
} );
Console.Read();
}
}
}
}

首先通过ChannelFactory<IService> 先后创建了10个Proxy对象,然后以异步的方式进行service的调用(为了简单起见,直接通过ThreadPool实现异步调用),到service调用结束将当前时间输出来。

我们来运行一下我们的程序,看看会出现怎样的现象。先来看看service端的输出结果:

image

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

image

二、解除线程的关联性

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

namespace Artech.ThreadAffinity2.Services
{
[ServiceBehavior(UseSynchronizationContext = false)]
public class Service:IService
{

……
}
}

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

image

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

image

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

分享到:
评论

相关推荐

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

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

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

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

    Apress Pro WCF 4

    Apress Pro WCF 4 Practical Microsoft SOA Implementation, 2nd Part I: Introducing Windows ...■Chapter 12: Developing Peer-to-Peer Applications with WCF ■Chapter 13: Implementing SOA Interoperability

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

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

    我的WCF之旅源代码 IIS寄宿

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

    WCF 我的WCF之旅示例代码

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

    WCF并发与实例管理

    WCF并发与实例管理 WCF并发与实例管理

    wcf多线程和异步操作

    wcf多线程和异步操作 异步服务的调用 异步服务的实现 读取文件demo

    我的WCF之旅后续篇

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

    WCF 学习之旅

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

    用例浅谈WCF实例与并发

    并发是对同时执行的任务数量的度量,单位为任务(如请求、作业、事务等)。执行时间是对完成任务所用时间的度量,单位为时间(如毫秒、秒等)。吞吐量是在固定的时间内完成的任务数量的度量,单位为任务/时间(如...

    WCF并发行为

    演示了WCF中同一个客户端实例怎样并发访问服务器实例,各个并发方式的设置和用法。

    WCF开发实战

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

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

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

    我的WCF之旅(合集)

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

    我的wcf之旅1-13

    Artech的博客文章,我把它转成chm的格式给大家分享

    MFC程序访问WCF服务

    本示例共包含四个模块,演示了C# WinForm承载WCF服务程序集,再使用C#程序集封装了对该WCF的访问API,然后在MFC对话框程序中封装的API,成功实现了对WCF的访问。 环境:VC++2010

    MFC 访问 WCF

    本示例共包含四个模块,演示了C# WinForm承载WCF服务程序集,再使用C#程序集封装了对该WCF的访问API,然后在MFC对话框程序调用中封装的API,成功实现了对WCF的访问。 环境:VC++2010

    《WCF按部就班学习系列8_WCF并发管理概述》

    WCF并发管理的概述,WCF服务并发模式、WCF实例模型和并发模型关系、限流概述,实现代码分析及运行结果和完整源码

    基于WCF多线程的SOCKET文件传输服务

    基于WCF多线程的SOCKET文件传输服务

Global site tag (gtag.js) - Google Analytics