2

There is a simple WCF service in the console application and the same client. The client simultaneously (in different threads) sends N requests to the server. It is expected that the server for processing concurrent requests simultaneously allocates several threads and after a second downtime (specially made by Thread.Sleep (1000)) simultaneously returns the answers to the client, but this does not happen. The service processes all requests in one thread (this is seen in ThreadId on the screenshot), although the ServiceBehavior attribute is set to ConcurrencyMode.Multiple.

Service app code:

namespace Server
{
  using System;
  using System.ServiceModel;
  using System.Threading;

  [ServiceContract]
  public interface IServer
  {
    [OperationContract]
    int GetResult(int value);
  }

  [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple,  
    InstanceContextMode = InstanceContextMode.Single)]
  public class Server: IServer
  {
    public int GetResult(int value)
    {
      Console.WriteLine("In: {0}, Time: {1}, ThreadId: {2}", 
                        value, DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId);

      Thread.Sleep(1000);

      Console.WriteLine("Out: {0}, Time: {1}, ThreadId: {2}",
                        value, DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId);
      return value;
    }
  }


  class Program
  {
    static void Main()
    {
      var host = new ServiceHost(new Server());
      host.Open();

      Console.WriteLine("Service started");

      Console.ReadLine();
    }
  }
}

Client app code:

using System;

namespace Server
{
  using System.ServiceModel;

  [ServiceContract]
  public interface IServer
  {
    [OperationContract]
    int GetResult(int value);
  }
}

namespace Client
{
  using System.ServiceModel;
  using System.Threading;

  using Server;

  class Program
  {
    static object lockObj = new object();

    static void Main()
    {
      ChannelFactory<IServer> factory = new ChannelFactory<IServer>("defaultEndPoint");
      IServer channel = factory.CreateChannel();


      const int threadCount = 6;
      int value = 0;

      for (int i = 0; i < threadCount; i++)
      {
        new Thread(state =>
          {
            int n;
            lock (lockObj)
            {
              value++;
              n = value;
            }
            Console.WriteLine("Send value = {0}, Time = {1}", n, DateTime.Now.TimeOfDay);
            n = channel.GetResult(n);
            Console.WriteLine("Response value = {0}, Time = {1}", n, DateTime.Now.TimeOfDay);
          }).Start();
      }

      Console.ReadLine();
    }
  }
}

Service config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="DefaultBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceThrottling maxConcurrentCalls="1000" maxConcurrentInstances="1000" maxConcurrentSessions="1000"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="Server.Server" behaviorConfiguration="DefaultBehavior">
        <endpoint contract="Server.IServer" binding="basicHttpBinding" address=""/>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:7803/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>

Console output on client side:

Send value = 1, Time = 11:20:13.1335555

Send value = 3, Time = 11:20:13.1335555

Send value = 2, Time = 11:20:13.1335555

Send value = 4, Time = 11:20:13.1335555

Send value = 5, Time = 11:20:13.1335555

Send value = 6, Time = 11:20:13.1335555

Response value = 3, Time = 11:20:14.6191583

Response value = 1, Time = 11:20:15.6184362

Response value = 2, Time = 11:20:16.6342291

Response value = 4, Time = 11:20:17.6497805

Response value = 5, Time = 11:20:18.6657260

Response value = 6, Time = 11:20:19.6820159

Console output on service side:

In: 3, Time: 11:20:13.5030783, ThreadId: 12

Out: 3, Time: 11:20:14.5184547, ThreadId: 12

In: 1, Time: 11:20:14.6035310, ThreadId: 12

Out: 1, Time: 11:20:15.6184362, ThreadId: 12

In: 2, Time: 11:20:15.6184362, ThreadId: 12

Out: 2, Time: 11:20:16.6342291, ThreadId: 12

In: 4, Time: 11:20:16.6342291, ThreadId: 12

Out: 4, Time: 11:20:17.6497805, ThreadId: 12

In: 5, Time: 11:20:17.6497805, ThreadId: 12

Out: 5, Time: 11:20:18.6657260, ThreadId: 12

In: 6, Time: 11:20:18.6657260, ThreadId: 12

Out: 6, Time: 11:20:19.6820159, ThreadId: 12

Roman
  • 33
  • 3
  • Possible duplicate of [Does WCF support Multi-threading itself?](https://stackoverflow.com/questions/3295045/does-wcf-support-multi-threading-itself) – gabba Mar 16 '18 at 08:34

2 Answers2

1

I realized exactly what was wrong. On the client, all threads used one copy of the channel. And it was necessary to create a separate channel in each thread. Like this:

ChannelFactory<IServer> factory = new ChannelFactory<IServer>("defaultEndPoint");
// !!! IServer channel = factory.CreateChannel();

const int threadCount = 6;
int value = 0;

for (int i = 0; i < threadCount; i++)
{
  new Thread(state =>
    {
      int n;
      lock (lockObj)
      {
        value++;
        n = value;
      }
      IServer channel = factory.CreateChannel(); // !!! 
      Console.WriteLine("Send value = {0}, Time = {1}", n, DateTime.Now.TimeOfDay);
      n = channel.GetResult(n);
      Console.WriteLine("                                          Response value = {0}, Time = {1}", n, DateTime.Now.TimeOfDay);
    }).Start();
}
Roman
  • 33
  • 3
1

This is because you limit yourself at the client to a single client instance that obviously doesn't support multiple concurrent calls for basic http binding.

Two options here:

  1. scale the client by creating an extra channel per thread:

    ChannelFactory<IServer> factory = new ChannelFactory<IServer>();
    
    const int threadCount = 6;
    int value = 0; 
    
    for (int i = 0; i < threadCount; i++)
    {
      new Thread(state =>
      {
        IServer channel = factory.CreateChannel();
    
        int n;
        lock (lockObj)
        {
            value++;
            n = value;
        }
    
        ...
    
  2. switch to WS binding that seem to support concurrency differently (you could have multiple threads created at the server despite a single instance at the client side)

Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
  • Thanks for the help! This works fine when the server and client are on the same computer. But as soon as I run them on different computers on the local network, the server starts to allocate only two threads. – Roman Mar 16 '18 at 11:54
  • The console output on the server side looks like this In: 4, Time: 15:50:24.86, ThreadId: 7 In: 2, Time: 15:50:24.86, ThreadId: 4                                                    Out: 2 Time: 15:50:25.89, ThreadId: 4                                                    Out: 4 Time: 15:50:25.89, ThreadId: 7 In: 6, Time: 15:50:25.94, ThreadId: 7 In: 5, Time: 15:50:25.94, ThreadId: 4                                                    Out: 5 Time: 15:50:26.95, ThreadId: 4                                                    Out: 6 Time: 15:50:26.95, ThreadId: 7 What could be the reason? – Roman Mar 16 '18 at 11:57
  • This could be because the default outbound connection limit throttles your client. Try setting `System.Net.ServicePointManager.DefaultConnectionLimit` to something higher. – Wiktor Zychla Mar 16 '18 at 14:49