1
votes

In my sample WCF console application i have set service behavior for service class as,

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Single, InstanceContextMode=InstanceContextMode.PerCall)]

and calling multiple operation of the service using same proxy object, shown below.

             proxy = new ConcurrencyServiceClient("ConcurrencyService_Tcp");

             proxy.BuyStock("MSFT", 100);

             proxy.BuyStock("GOOG", 50);

             proxy.BuyStock("F", 500);

             proxy.SellStock("AAPL", 100);

             proxy.SellStock("ORCL", 300);

             proxy.SellStock("GM", 75);

In above case it is calling each operation sequentially but if i choose concurrency mode as 'Multiple' instead of 'Single', it is executing operations concurrently.

I have read at many places that ‘ConcurrencyMode has no effect on PerCall services as by definition each call from a client gets allocated a new service instance with its own thread: PerCall is always effectively ConcurrencyMode.Single.’

I am using netTcpBinding binding.

Problem is really killing me, please advise me about this behavior.

Thanks!

1
The service behaviour is completely divorced from the client usage. As such, I've no idea what this question is even asking.Damien_The_Unbeliever
"In above case it is calling each operation sequentially but if i choose concurrency mode as 'Multiple' instead of 'Single', it is executing operations concurrently." are you sure this is happening, how did you check?Scott Chamberlain
In BuyStock and SellStock service operations i am printing some code on console and sleeping for 1 second. so to execute all 6 calls it is taking 6 seconds with single concurrency but just 2-3 seconds with multiple concurrency.Vaibhav Raut

1 Answers

2
votes

Is Concurrency mode relevant if we use PerCall instancing?

Usually not. If you are always creating a new instance for every call with ConcurrencyMode.PerCall, there is only ever a single thread in each instance... hence inherently thread safe.

The only exception is if service "A" makes a synchronous call out to service "B" and then service "B" makes a call back to the same instance of service "A". This is specifically the ConcurrencyMode.Reentrant case.

Looking program output that you posted:

The confusion is that you are using the same proxy object. The serialization is on the client, not the service. Proxies are thread safe, but will only send one request to the service at a time.

I wrote a command line program below that you can run:

  • The service method will sleep for 1 sec before returning
  • The client method executes a loop twice.
    • Within each loop it creates 10 threads to call the service ten times.
    • In the first loop it creates a new proxy for each call - and you see concurrency.
    • In the second loop it shares the same proxy across threads - and calls are serialized.

When I switch between ConcurrencyMode.Multiple and ConcurrencyMode.Single I'm not seeing any difference in the output.

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Threading.Tasks;

using System.Collections.Generic;

namespace ConsoleWCF
{
    [ServiceContract]
    public interface ISimple
    {
        [OperationContract]
        int GiveItBack(int i);
    }

    //// Different ConcurrencyMode does NOT change the output.
    //[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Single, InstanceContextMode=InstanceContextMode.PerCall)]
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall)]
    public class SimpleService : ISimple
    {
        public int GiveItBack(int i)
        {
            Console.WriteLine("Return " + i);
            System.Threading.Thread.Sleep(1000);
            return i;
        }
    }

    public static class Program
    {
        static void Main(string[] args)
        {
            ServiceHost simpleHost = new ServiceHost(typeof(SimpleService), new Uri("http://localhost/Simple"));
            simpleHost.Open();

            ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(simpleHost.Description.Endpoints[0]);
            List<Task> tasks = new List<Task>();


            Console.WriteLine("{0}{0}Start proxy per call....", Environment.NewLine);
            var stopwatch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < 10; i++)
            {
                int value = i;
                tasks.Add(Task.Factory.StartNew(() =>
                    {
                        // proxy per call...
                        ISimple proxy = factory.CreateChannel();    // <-------- new Proxy
                        Console.WriteLine("Client    - Sending " + value);
                        int response = proxy.GiveItBack(value);
                        Console.WriteLine("Client    - Got back " + response);

                        ((ICommunicationObject)proxy).Shutdown();
                    }));
            }

            Task.WaitAll(tasks.ToArray());
            Console.WriteLine("Finished in {0} msec", stopwatch.ElapsedMilliseconds);

            Console.WriteLine("{0}{0}Start Shared proxy....", Environment.NewLine);
            ISimple sharedProxy = factory.CreateChannel();    // <-------- one one Proxy
            stopwatch = System.Diagnostics.Stopwatch.StartNew();

            for (int i = 0; i < 10; i++)
            {
                int value = i;
                tasks.Add(Task.Factory.StartNew(() =>
                {
                    // proxy per call...
                    Console.WriteLine("Client    - Sending " + value);
                    int response = sharedProxy.GiveItBack(value);
                    Console.WriteLine("Client    - Got back " + response);
                }));
            }

            Task.WaitAll(tasks.ToArray());

            ((ICommunicationObject)sharedProxy).Shutdown();

            Console.WriteLine("Finished in {0} msec", stopwatch.ElapsedMilliseconds);

            Console.WriteLine("Press ENTER to close the host once you see 'ALL DONE'.");
            Console.ReadLine();

            factory.Shutdown();
            simpleHost.Shutdown();
        }
    }

    public static class Extensions
    {
        static public void Shutdown(this ICommunicationObject obj)
        {
            try
            {
                obj.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Shutdown exception: {0}", ex.Message);
                obj.Abort();
            }
        }
    }
}

Sample output:

Start proxy per call....
Client    - Sending 0
Client    - Sending 1
Client    - Sending 2
Client    - Sending 3
Return 1
Return 2
Return 0
Return 3
Client    - Sending 4
Return 4
Client    - Got back 2
Client    - Got back 1
Client    - Got back 0
Client    - Got back 3
Client    - Sending 5
Client    - Sending 6
Client    - Sending 7
Client    - Sending 8
Return 5
Return 6
Return 7
Return 8
Client    - Sending 9
Client    - Got back 4
Return 9
Client    - Got back 6
Client    - Got back 8
Client    - Got back 5
Client    - Got back 7
Client    - Got back 9
Finished in 3009 msec


Start Shared proxy....
Client    - Sending 0
Client    - Sending 3
Client    - Sending 5
Client    - Sending 2
Client    - Sending 1
Client    - Sending 4
Return 0
Client    - Sending 6
Client    - Got back 0
Client    - Sending 7
Return 3
Client    - Sending 8
Client    - Got back 3
Client    - Sending 9
Return 5
Client    - Got back 5
Return 2
Client    - Got back 2
Return 1
Client    - Got back 1
Return 4
Client    - Got back 4
Return 6
Client    - Got back 6
Return 7
Client    - Got back 7
Return 8
Client    - Got back 8
Return 9
Client    - Got back 9
Finished in 10027 msec
Press ENTER to close the host once you see 'ALL DONE'.

Why the loop that creates separate proxies still takes 3 sec to run instead of 1 sec is probably due to a throttle or thread limit somewhere... its not the WCF behavior that's limiting it.