WCF服务是否使用多线程来处理传入的请求?

6

我该如何确保WCF服务使用线程池来处理传入消息?

目前简单的方法调用,如“return null;”花费约45秒时间,而其他请求正在处理中。

以下是我注释过的服务类代码:

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    public partial class MyService : IMyService {
...
}

但是当我在任务管理器中观察进程时,它似乎使用了恒定数量的线程,即使在负载下也是如此。


public ActionResult SelectDatabase(string param)
        {
            if (!String.IsNullOrEmpty(param))
            {
            try
            {
                MyServicece svc = new MyService();
                Database[] dbsArray = svc.GetDatabases(param);
                if (depsArray != null)
                    ViewData["depsArray"] = depsArray;

                return View();
            }
            catch (Exception exc)
            {
                // log here                
                return ActionUnavailable();
            }
        }

这是我的服务行为:

<?xml version="1.0"?>
<configuration>
  <runtime>

  </runtime>
  <system.net>
    <connectionManagement>
      <add address="*" maxconnection="100" />
    </connectionManagement>
  </system.net>
  <system.serviceModel>
    <diagnostics performanceCounters="Default" />
    <bindings>      
      <netTcpBinding>
        <binding sendTimeout="00:02:00" receiveTimeout="00:02:00" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" maxBufferPoolSize="2147483647">
          <security mode="None">           
          </security>
        </binding>
      </netTcpBinding>
    </bindings>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <behaviors>
      <endpointBehaviors>
        <behavior name="CrossDomainServiceBehavior">
          <webHttp />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="MyService.MyServiceBehavior">
          <serviceThrottling maxConcurrentCalls="100"   maxConcurrentInstances="100" maxConcurrentSessions="100" />
          <dataContractSerializer maxItemsInObjectGraph="2147483646"/>
          <serviceMetadata httpGetEnabled="false" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="MyService.MyServiceBehavior" name="MyService.MyService">
        <endpoint address="MyService" binding="netTcpBinding" contract="AService.IAServ"  isSystemEndpoint="false" />
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
      </service>
      <service behaviorConfiguration="MyService.MyServiceBehavior" name="MyService.MyServiceAdmin">
        <endpoint address="MyServiceAdmin" binding="netTcpBinding" contract="MyService.IMyServiceAdmin"  isSystemEndpoint="false" />
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />        
      </service>
    </services>
  </system.serviceModel>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

以下是我创建服务实例的步骤:

ServiceHost myserviceHost = new ServiceHost(typeof(MyService), new Uri(String.Format("net.tcp://{0}/", _bindAddress)));
            myserviceHost.Open();
            Console.WriteLine(myserviceHost.BaseAddresses[0]);

4
InstanceContextMode = Single 表示您只有一个单独的服务实例,这是不太可伸缩的!ConcurrencyMode = Multiple 表示该单例可以同时处理多个服务请求 - 但也意味着您的服务实现代码必须是百分之百线程安全的,这并不容易!我建议使用 InstanceContextMode.PerCallConcurrencyMode.Single - 这样,每个传入的 WCF 请求都会获得自己的服务类实例来处理请求。WCF 运行时可以处理多个并发请求,您的代码编写起来也很容易。 - marc_s
可能重复:http://stackoverflow.com/questions/1609703/wcf-concurrencymode-multiple-connection-best-practices-and-caching - Christopher Rathermel
1
有时候 InstanceContextMode = Single 是可以的 - 比如当 API 是无状态并且只是转发调用时。没有必要创建超过一个服务实例。例如返回队列长度或者将数据提交到处理队列的方法调用...对于我们中的一些人来说,编写100%线程安全的代码是标准行为,而不是每个人都是脚本小子。 - TomTom
@marc_s 请查看我的更新问题。 - kseen
1个回答

7

InstanceContextMode和ConcurrencyMode是分开的概念,但它们之间存在一定的相互作用 - 我之前在博客中详细介绍过。

WCF调用在IO线程池线程上处理。假设您没有像ConcurrencyMode.SingleInstanceContextMode.Single这样的操作,这将使每个对服务的调用序列化,线程池管理器将尝试平衡线程数与工作速率。

如果可以通过5个线程处理并发请求,则会使用这么多线程。您可能会看到线程池可以跟上您所见到的线程数量的工作速率。您可以很愉快地使用比内核更多的线程,因为只要线程不仅仅是CPU绑定的,操作系统就可以通过在以前运行的线程开始IO时将线程切换到CPU来获得吞吐量。如果CPU完全达到最大值,那么线程池管理器的启发式算法将使其不愿向线程池添加更多线程。

然而,还有另外两个潜在问题:

  1. 基于Session的绑定在客户端上可能会阻塞,而同时通过同一代理进行多个并发出站请求。您没有说明如何生成多个请求,因此这可能是一个问题;
  2. 您可能还会看到限流 kicking in,因为在.NET 4之前,默认最大并发请求数为16,而默认并发会话数为10。这些值已在.NET 4中提高,但您没有说明您使用的.NET版本。

这是你的问题 - 你已经达到了MaxConcurrentSessions限制的饱和点。每个请求都必须等待前一个会话超时。关闭或重用代理,你应该会看到吞吐量的增加 - 我在这里写了关于会话的博客http://www.dotnetconsult.co.uk/weblog2/PermaLink,guid,af6e6325-2e30-42e3-acb9-57e1363fa51e.aspx - Richard Blewett
我在我的配置文件中设置了 maxConcurrentCalls = "100"。你真的认为我会达到100个并发调用吗? - kseen
不,正如我在评论中所说的那样,你正在触发会话限制。当你创建一个新代理并使用它时,它会建立一个会话。除非你关闭代理,否则默认情况下会话将保持活动状态20分钟。当你在不关闭代理的情况下使用网站时,最终会达到100个核心的会话限制(在.NET 4上的默认值)。只有当会话超时时,才会有新的调用进来。 - Richard Blewett
我重写了我的代码,创建了一个全局应用程序代理,然后在所有其他控制器方法中使用它,并进行预检查和重新创建(如果需要)。这是我们应该走的正确路径吗? - kseen
为了简单起见,我倾向于保持代码不变,但在每个请求结束时关闭代理,并仅在看到它引起性能问题时更改它(代理创建和拆除不是免费的)。我有时会看到代理在使用多个线程和单个具有NetTcpBinding的代理时在客户端上阻塞 - 尽管我现在忘记了根本原因。 - Richard Blewett
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接