WCF 并发(单线程、多线程和重入)与节流






4.91/5 (161投票s)
一篇关于 WCF 并发和节流的文章。
目录
- 引言和目标
- 先决条件
- 为什么我们需要 WCF 的并发性?
- WCF 并发性和实例——两个不同的概念
- 三种 WCF 并发类型
- 示例代码演示
- 实例与并发的 9 种组合
- 实例模式 = 按调用 和 并发 = 单线程
- 实例模式 = 按调用 和 并发 = 多线程
- 实例模式 = 按会话 和 并发 = 单线程
- 实例模式 = 按会话 和 并发 = 多线程
- 实例模式 = 单例 和 并发 = 单线程
- 实例模式 = 单例 和 并发 = 多线程
- 重入
- 节流行为
- WCF 节流的默认值
- 参考文献
- 我写的其他 WCF 文章
引言和目标
在本文中,我们将重点关注 WCF 并发和节流。我们将首先尝试理解什么是 WCF 并发以及三种重要的 WCF 并发类型。然后,我们将通过单线程和多线程的示例来展示 WCF 并发。之后,我们将介绍 WCF 并发与实例的 9 种组合。最后,我们将尝试理解如何通过 WCF 的‘web.config’文件配置节流。
图片来源:http://www.nassaulibrary.org/ncla/nclacler_files/LILC7.JPG。
先决条件
为了很好地理解 WCF 并发,理解 WCF 实例化的概念非常重要。因此,在继续阅读本文之前,请先阅读关于 WCF 实例化的文章在这里。
为什么我们需要 WCF 的并发性?
如果您在网上搜索“并发”的字典含义,您会找到以下定义:
“同时发生”
WCF 并发性帮助我们配置 WCF 服务实例如何同时为多个请求提供服务。您需要 WCF 并发性是出于以下主要原因;当然也可能有其他原因,但这些是最重要的原因。
默认情况下,WCF 服务在给定时刻只处理一个请求。
- 提高吞吐量:很多时候您希望增加 WCF 服务实例在任何给定时间点所完成的工作量。换句话说,您希望提高吞吐量。吞吐量是指给定事物可以完成的工作量。
- 与遗留系统集成:很多时候,您的 WCF 服务会与 VB6、COM 等遗留系统进行交互。这些系统很可能不是多线程的,换句话说,它们在任何给定时间只处理一个请求。因此,即使您的 WCF 服务具有并发能力,您仍然希望一次只处理一个请求。这可以通过将节流与 WCF 并发能力结合来实现。
WCF 并发性和实例——两个不同的概念
在我们之前的文章中,我们讨论了 WCF 实例。WCF 实例和 WCF 并发是两个不同的概念。WCF 实例决定了对象的创建方式,而 WCF 并发决定了 WCF 对象可以处理多少请求。
我理解我们的许多开发人员朋友都知道这个区别,但随着您深入研究 WCF 并发,它与 WCF 实例也有很多联系,所以我只是想强调一下区别。
WCF 实例提供了三个级别的 WCF 对象实例控制:按调用、按会话和单例。如果您不了解,请阅读我的文章:WCF 实例管理的 3 种方法。同样,WCF 也有三种实现 WCF 并发的方法。
三种 WCF 并发类型
在 WCF 中,您可以通过三种方式处理并发:单线程、多线程和重入。要指定 WCF 并发,我们需要使用 ServiceBehavior
标签,如下所示,并设置适当的‘ConCurrencyMode
’属性值。
单线程 (Single):在给定时刻,只有一个请求可以访问 WCF 服务对象。因此,在任何给定时刻,只有一个请求会被处理。其他请求必须等到 WCF 服务正在处理的请求完成后才能开始。
多线程 (Multiple):在这种场景下,WCF 服务对象可以在任何给定时刻处理多个请求。换句话说,请求通过在 WCF 服务器对象上生成多个线程来同时处理。因此,这里的吞吐量很高,但您需要确保与 WCF 服务器对象相关的并发问题。
重入 (Reentrant):单个请求线程可以访问 WCF 服务对象,但该线程可以退出 WCF 服务以调用另一个 WCF 服务,也可以通过回调调用 WCF 客户端并重新进入而不会死锁。
示例代码演示
为了演示这一点,让我们创建一个简单的示例代码,如下所示。我们将创建一个简单的 WCF 服务,其中包含一个名为 Call
的方法。当客户端调用此 WCF 服务时,它将显示以下详细信息:
- 发起调用的客户端名称。当客户端想要调用 WCF 服务时,此值将作为输入提供。
- 实例号,这将表示正在为请求提供服务的 WCF 实例数量。
- 执行该方法的线程号。
- 调用
Call
方法的时间。
以下是 Call
方法的服务契约。请注意,OperationContract
定义的 IsOneWay
设置为 true
。
[ServiceContract]
public interface IHelloWorldService
{
[OperationContract(IsOneWay=true)]
void Call(string ClientName);
}
以下是服务契约 IHelloWorldService
接口的简单实现。它有一个实例变量 i
,用于维护实例计数器,以及一个简单的 Console.Writeline
,用于显示客户端名称、实例号、线程号以及调用方法的时间。
此方法使用‘Thread.Sleep
’函数等待 5 秒钟。
public class HelloWorldService : IHelloWorldService
{
// maintain instance count
public int i;
public void Call(string ClientName)
{
// increment instance counts
i++;
// display client name, instance number , thread number and time when
// the method was called
Console.WriteLine("Client name :" + ClientName + " Instance:" +
i.ToString() + " Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() +
" Time:" + DateTime.Now.ToString() + "\n\n");
// Wait for 5 seconds
Thread.Sleep(5000);
}
}
此 WCF 服务将使用 WsHttpBinding
进行自托管,如下所示。我们选择自托管是为了能够监控 WCF 服务的时间、线程和实例数量。
static void Main(string[] args)
{
//Create a URI to serve as the base address
Uri httpUrl = new Uri("https://:8010/MyService/HelloWorld");
//Create ServiceHost
ServiceHost host = new ServiceHost(typeof(ClassLibrary1.HelloWorldService), httpUrl);
//Add a service endpoint
host.AddServiceEndpoint(typeof(ClassLibrary1.IHelloWorldService), new WSHttpBinding(), "");
//Enable metadata exchange
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
host.Description.Behaviors.Add(smb);
//Start the Service
host.Open();
Console.WriteLine("Service is host at " + DateTime.Now.ToString());
Console.WriteLine("Host is running... Press <Enter> key to stop");
Console.ReadLine();
}
在客户端,我们将同时向 WCF 服务发出五个连续的调用。以下是对应的代码片段:
Console.WriteLine("Enter Client name");
string str = Console.ReadLine();
ServiceReference1.HelloWorldServiceClient obj =
new ServiceReference1.HelloWorldServiceClient();
for(int i=0;i<5;i++)
{
obj.Call(str);
}
如果您运行上述项目并监视您的 WCF 服务器服务,您将看到截图。有两点需要注意:
- 当使用“按调用”运行时,每次服务运行时都会创建新的实例。“实例 1”表示为每个方法调用创建的新实例。
- 默认情况下,并发设置为单线程,因此所有五个从 WCF 客户端发出的请求都将使用同一线程进行处理。您可以看到线程的值始终是 4。
- 最重要的是时间。由于并发设置为单线程,调用将按顺序一个接一个地进行。您可以从方法调用之间 5 秒的时间差中注意到这一点。
让我们将并发模式更改为多线程。要将并发模式更改为多线程,我们需要指定 Multiple
作为并发模式,如下面的代码片段所示。
如果您现在运行客户端,您可以看到不同的线程(即线程 4、5、6、7 和 8)被生成来处理请求,并且方法调用的时间几乎相同。换句话说,方法在不同的线程上并发执行。
简而言之,您现在可以在更少的时间内获得更高的吞吐量。
实例与并发的 9 种组合
如下表所示,有 9 种实例与并发的组合。在下面的部分中,我们将更详细地讨论这一点。
InstanceContext 模式 | ConcurrencyMode | ||
单线程(默认) | 多个 | 重入 | |
Single (所有客户端只有一个实例) | 所有客户端使用单个线程。 | 所有客户端使用多个线程。 | 所有客户端使用单个线程,当调用被转到其他 WCF 服务时,锁会被释放。 |
按会话(默认) (每个客户端有多个实例) | 每个客户端使用单个线程。 | 每个请求使用多个线程。 | 所有客户端使用单个线程,当调用被转到其他 WCF 服务时,锁会被释放。 |
按调用(每次方法调用都有多个实例) | 每个客户端使用单个线程。 | 每个客户端使用单个线程 | 所有客户端使用单个线程,当调用被转到其他 WCF 服务时,锁会被释放。 |
实例模式 = 按调用 和 并发 = 单线程
对于“按调用”,每次调用 WCF 服务器服务时都会创建新的 WCF 实例。默认并发设置为单线程,因此所有实例将只使用一个线程来处理。
以下是“按调用”和“单线程”并发场景的简单图示:
- 对于每个客户端实例,将分配一个线程。
- 对于每次方法调用,都会创建一个新的服务实例。
- 将使用单个线程来服务从单个客户端实例生成的所有 WCF 实例。
如果您参考之前的示例,您可以看到线程是相同的,并且 WCF 服务有 5 秒的停顿。
实例模式 = 按调用 和 并发 = 多线程
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class HelloWorldService : IHelloWorldService
{
}
在此组合中,每次调用都会创建多个实例,但多个线程会为 WCF 服务实例的每个方法调用提供服务。您可以在下图看到,我们有两个 WCF 服务实例,每个实例都有多个为每次方法调用创建的 WCF 服务对象。每次方法调用都由多个线程处理。
在上面的示例中,如果您还记得,我们有多个线程为每个方法调用提供服务,并且每次方法调用之间的时间差大大减小。方法调用大约同时触发。
实例模式 = 按会话 和 并发 = 单线程
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession,
ConcurrencyMode = ConcurrencyMode.Single)]
public class HelloWorldService : IHelloWorldService
{
}
在此组合中,每个 WCF 客户端会话都会创建一个 WCF 服务实例,因为 WCF 实例模式设置为“按会话”。所有方法调用都按顺序一个接一个地执行。换句话说,对于某个特定的服务实例,所有方法调用只有一个线程可用。
如果您使用“按会话”和“单线程”组合模式运行示例代码,您会发现同一个线程会执行所有请求,并且每个会话都有相同的 WCF 实例。
实例模式 = 按会话 和 并发 = 多线程
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class HelloWorldService : IHelloWorldService
{
}
实例模式‘PerSession
’并发是 WCF 中的默认设置。
在此组合中,每个 WCF 客户端会话都会创建一个 WCF 实例,并且每个方法调用都通过多个线程运行。下图是其图示:
如果您运行本文附带的示例代码,您会发现每次方法调用都使用同一个实例,并且在不同的线程上运行。
为了更好地理解,您可以运行不同的客户端 exe 实例并使用不同的名称,如下所示。您会注意到每个客户端如何获得自己的 WCF 服务实例,并且每个方法都分配给不同的线程来运行。
实例模式 = 单例 和 并发 = 单线程
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Single)]
public class HelloWorldService : IHelloWorldService
{
}
在此组合中,只创建一个 WCF 服务实例,该实例为从所有 WCF 客户端发送的所有请求提供服务。这些请求仅使用一个线程来处理。
您可以在下图看到,大约为每个 WCF 客户端调用分配了一个线程,并且只创建了一个 WCF 服务实例。
实例模式 = 单例 和 并发 = 多线程
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class HelloWorldService : IHelloWorldService
{}
在此组合中,创建一个 WCF 服务实例来服务所有 WCF 客户端。所有请求都使用多个/不同的线程来处理。
从输出中可以看到,我们有 6 个线程处理 10 个请求,而之前大约只有两个线程。
重入
在此模式下,只有一个线程运行来处理所有请求。如果您的 WCF 服务向另一个 WCF 服务发出出站调用,或者发出客户端调用,它会释放线程锁。换句话说,在出站调用完成之前,其他 WCF 客户端无法发起调用。
节流行为
WCF 节流帮助您为并发调用、WCF 实例和并发会话的数量设置上限。WCF 提供了三种方式来定义上限:MaxConcurrentCalls
、MaxConcurrentInstances
和 MaxConcurrentSessions
。
MaxConcurrentCalls
:限制 WCF 服务实例可以处理的并发请求数量。MaxConcurrentInstances
:限制在给定时间可以分配的服务实例数量。对于“按调用”服务,此值与并发调用数匹配。对于“按会话”服务,此值等于活动会话实例数。此设置对于单例实例化模式无关紧要,因为只创建一个实例。MaxConcurrentSessions
:限制服务允许的活动会话数量。
以上所有三个设置都可以在 servicebehaviors
标签中定义,如下面的 XML 片段所示:
<serviceBehaviors>
<behavior name="serviceBehavior">
<serviceThrottling maxConcurrentCalls="16"
maxConcurrentInstances="2147483647" maxConcurrentSessions="10" />
</behavior>
</serviceBehaviors>
WCF 节流的默认值
下表显示了不同 WCF 版本的节流默认设置:
MaxConcurrentSessions | MaxConcurrentCalls | MaxConcurrentInstances | |
WCF 3.0 / 3.5 | 6 | 26 | 10 |
WCF 4.0 | 16 * 处理器数 MaxConcurrentCalls | MaxConcurrentCalls + MaxConcurrentSessions 100 * 处理器数 | 100 * 处理器数 |
参考文献
- Kenny Wolf 先生关于节流的精彩信息:http://kennyw.com/work/indigo/150。
- Rick Rain 先生写的一篇分为三部分的精彩文章,深入解释了并发和节流:http://blogs.msdn.com/b/rickrain/archive/2009/06/15/wcf-instancing-concurrency-and-throttling-part-1.aspx。
- Michele Leroux Bustamante 先生讨论了使用 WCF 并发提高 WCF 服务吞吐量的设置:http://www.windowsitpro.com/article/net-framework2/concurrency-and-throttling-configurations-for-wcf-services.aspx。
- Justin Smith 解释了一个示例代码,说明
InstanceContextMode
和ConcurrencyMode
如何影响并发:http://www.wintellect.com/CS/blogs/jsmith/archive/2006/05/16/instancecontextmode-and-concurrencymode.aspx。 - Glav 先生讨论了节流和遗留系统:http://weblogs.asp.net/pglavich/archive/2007/06/27/wcf-and-concurrent-usage-throttling.aspx。
- WCF 节流的默认值:http://blogs.msdn.com/b/wenlong/archive/2009/07/26/wcf-4-higher-default-throttling-settings-for-wcf-services.aspx。
我写的其他 WCF 文章
您可以参考我写的其他一些关于 WCF 的文章:
- WCF FAQ 第 1 部分:https://codeproject.org.cn/KB/aspnet/WCF.aspx
- WCF FAQ 第 2 部分:https://codeproject.org.cn/KB/aspnet/WCFPart2.aspx
- WCF FAQ 第 3 部分:WCFFAQPart3.aspx
- WCF FAQ 第 4 部分:WCFFAQPart5.aspx
- WCF 事务:WCFTransactions.aspx
- WCF 实例化:WCFInstance.aspx
- 绑定:WCFBasicHttpBinding.aspx
- HttpBinding.aspx
如需进一步阅读,请观看以下面试准备视频和分步视频系列。