Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple parallel calls to WCF Service takes longer than single call

I'm testing WCF concurrency and instancing.

There is wcf Service :

public class Service1 : IService1
{
    public string GetData(int value)
    {
        Thread.Sleep(1000);
        return string.Format("You entered: {0}", value);
    }

}

From my forms application I make call to this service method. When I do single call, it takes aprox: 1 sec as expected.

    private void single_Click(object sender, EventArgs e)
    {
        using (var service = new Service1Client())
        {
            var sw = new Stopwatch();
            sw.Start();
            service.GetData(1);
            sw.Stop();
            Debug.WriteLine(sw.Elapsed);
        }
    }

But when I call it multiple times with Tasks, it takes aprox : call count * 1 second.

    private void mult_Click(object sender, EventArgs e)
    {
        using (var service = new Service1Client())
        {
           var tasks = new List<Task<string>>();
           for (var i = 0; i < 5; i++)
           {

              int p = i;
              tasks.Add(Task.Factory.StartNew(() => service.GetData(p)));
           }


           var sw = new Stopwatch();
           sw.Start();
           Task.WaitAll(tasks.ToArray());
           sw.Stop();
           Debug.WriteLine(sw.Elapsed);

           foreach (var task in tasks)
           {
               Debug.WriteLine(task.Result);
           }


        }
    }

I've tried all 9 combinations of Instancing and Concurrency (Instance mode = Per Call and Concurrency = Single etc.)

Interesting thing is that if I create new ServiceClient object for all Task, it works fine, but I don't think it is right approach. I feel that there must be some thing I missed.If so, Can you tell me what exactly?

like image 406
Dadroid Avatar asked Jun 24 '15 17:06

Dadroid


1 Answers

The issue is on the client side.

You have to explicitly call Open() on the Service1Client object before making any calls to the service. Otherwise your WCF client proxy is internally going to have a call to EnsureOpened(). The problem is specifically that EnsureOpened() will result in each request waiting until the previous request is completed before executing and this is why only one request will be sent out at a time and not in parallel as desired.

Change your code like this:

using (var service = new Service1Client())
{
    service.Open();
    // Do stuff...
}

From Wenlong Dong's excellent blog post on the subject:

If you don’t call the “Open” method first, the proxy would be opened internally when the first call is made on the proxy. This is called auto-open. Why? When the first message is sent through the auto-opened proxy, it will cause the proxy to be opened automatically. You can use .NET Reflector to open the method System.ServiceModel.Channels.ServiceChannel.Call and see the following code:

if (!this.explicitlyOpened)
  {
         this.EnsureDisplayUI();
         this.EnsureOpened(rpc.TimeoutHelper.RemainingTime());
  } 

When you drill down into EnsureOpened, you will see that it calls CallOnceManager.CallOnce. For non-first calls, you would hit SyncWait.Wait which waits for the first request to complete. This mechanism is to ensure that all requests wait for the proxy to be opened and it also ensures the correct execution order. Thus all requests are serialized into a single execution sequence until all requests are drained out from the queue. This is not a desired behavior in most cases.

like image 126
Derek W Avatar answered Nov 18 '22 11:11

Derek W