65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (161投票s)

2010 年 6 月 26 日

CPOL

12分钟阅读

viewsIcon

776860

downloadIcon

7700

一篇关于 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 提供了三种方式来定义上限:MaxConcurrentCallsMaxConcurrentInstancesMaxConcurrentSessions

  • 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 * 处理器数

参考文献 

我写的其他 WCF 文章

您可以参考我写的其他一些关于 WCF 的文章:

如需进一步阅读,请观看以下面试准备视频和分步视频系列。

© . All rights reserved.