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);
}
[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(() =>
{
ISimple proxy = factory.CreateChannel();
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();
stopwatch = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
int value = i;
tasks.Add(Task.Factory.StartNew(() =>
{
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.