La frase clave en la lectura de la declaración de Lowy es "en interés del rendimiento". Lowy señala que al usar ConcurrencyMode.Single WCF implementará ciegamente un bloqueo para imponer la serialización a la instancia del servicio. Los bloqueos son caros y este no es necesario porque PerCall ya garantiza que un segundo subproceso nunca intentará llamar a la misma instancia de servicio.
En términos de comportamiento: ConcurrencyMode no es relevante para una instancia de servicio PerCall.
En términos de rendimiento: servicio Un PerCall que es ConcurrencyMode.Multiple debe ser un poco más rápido porque no es crear y adquirir el bloqueo de rosca (que no sean necesarios) que ConcurrencyMode.Single está utilizando.
Escribí un programa de referencia rápido para ver si podía medir el impacto en el rendimiento de Simple vs Múltiple para un servicio PerCall: El índice de referencia no mostró diferencias significativas.
He pegado el siguiente código si quiere intentar ejecutarlo usted mismo.
casos de prueba I trataron:
- 600 hilos de llamar a un servicio 500 veces
- de 200 hilos llamar a un servicio 1000 veces
- 8 hilos de llamar a un servicio 10000 veces llamadas
- 1 hilo un servicio 10000 veces
Ejecuté esto en una máquina virtual de 4 CPU ejecutando Service 2008 R2. Todos los casos, excepto el de 1 hilo, estaban limitados por la CPU.
Resultados: Todas las ejecuciones estuvieron dentro de aproximadamente el 5% de cada uno. A veces ConcurrencyMode.Multiple era más rápido. A veces, ConcurrencyMode.Single era más rápido. Tal vez un análisis estadístico adecuado podría elegir un ganador. En mi opinión, están lo suficientemente cerca como para no importar.
Aquí hay una salida típica:
partir individual de servicio en net.pipe: // localhost/base ... type = SingleService THREADCOUNT = 600 = 500 ThreadCallCount tiempo de ejecución: 45156759 garrapatas 12615 mseg
partir múltiple de servicio en net.pipe: // localhost/base ... Tipo = MultipleService tHREADCOUNT = 600 = 500 ThreadCallCount r untime: 48731273 garrapatas 13613 ms
partir individual de servicio en net.pipe: // localhost/base ... Tipo = SingleService THREADCOUNT = 600 = 500 ThreadCallCount tiempo de ejecución: 48701509 garrapatas 13605 ms
partir múltiple de servicio en net.pipe: // localhost/base ... Tipo = MultipleService tHREADCOUNT = 600 = 500 ThreadCallCount tiempo de ejecución: 48590336 garrapatas 13574 ms
Código de Referencia:
advertencia habitual: Este es el código de prueba que toma atajos que no son apropiados para su uso en producción.
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WCFTest
{
[ServiceContract]
public interface ISimple
{
[OperationContract()]
void Put();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
public class SingleService : ISimple
{
public void Put()
{
//Console.WriteLine("put got " + i);
return;
}
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MultipleService : ISimple
{
public void Put()
{
//Console.WriteLine("put got " + i);
return;
}
}
public class ThreadParms
{
public int ManagedThreadId { get; set; }
public ServiceEndpoint ServiceEndpoint { get; set; }
}
public class BenchmarkService
{
public readonly int ThreadCount;
public readonly int ThreadCallCount;
public readonly Type ServiceType;
int _completed = 0;
System.Diagnostics.Stopwatch _stopWatch;
EventWaitHandle _waitHandle;
bool _done;
public BenchmarkService(Type serviceType, int threadCount, int threadCallCount)
{
this.ServiceType = serviceType;
this.ThreadCount = threadCount;
this.ThreadCallCount = threadCallCount;
_done = false;
}
public void Run(string baseAddress)
{
if (_done)
throw new InvalidOperationException("Can't run twice");
ServiceHost host = new ServiceHost(ServiceType, new Uri(baseAddress));
host.Open();
Console.WriteLine("Starting " + ServiceType.Name + " on " + baseAddress + "...");
_waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
_completed = 0;
_stopWatch = System.Diagnostics.Stopwatch.StartNew();
ServiceEndpoint endpoint = host.Description.Endpoints.Find(typeof(ISimple));
for (int i = 1; i <= ThreadCount; i++)
{
// ServiceEndpoint is NOT thread safe. Make a copy for each thread.
ServiceEndpoint temp = new ServiceEndpoint(endpoint.Contract, endpoint.Binding, endpoint.Address);
ThreadPool.QueueUserWorkItem(new WaitCallback(CallServiceManyTimes),
new ThreadParms() { ManagedThreadId = i, ServiceEndpoint = temp });
}
_waitHandle.WaitOne();
host.Shutdown();
_done = true;
//Console.WriteLine("All DONE.");
Console.WriteLine(" Type=" + ServiceType.Name + " ThreadCount=" + ThreadCount + " ThreadCallCount=" + ThreadCallCount);
Console.WriteLine(" runtime: " + _stopWatch.ElapsedTicks + " ticks " + _stopWatch.ElapsedMilliseconds + " msec");
}
public void CallServiceManyTimes(object threadParams)
{
ThreadParms p = (ThreadParms)threadParams;
ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(p.ServiceEndpoint);
ISimple proxy = factory.CreateChannel();
for (int i = 1; i < ThreadCallCount; i++)
{
proxy.Put();
}
((ICommunicationObject)proxy).Shutdown();
factory.Shutdown();
int currentCompleted = Interlocked.Increment(ref _completed);
if (currentCompleted == ThreadCount)
{
_stopWatch.Stop();
_waitHandle.Set();
}
}
}
class Program
{
static void Main(string[] args)
{
BenchmarkService benchmark;
int threadCount = 600;
int threadCalls = 500;
string baseAddress = "net.pipe://localhost/base";
for (int i = 0; i <= 4; i++)
{
benchmark = new BenchmarkService(typeof(SingleService), threadCount, threadCalls);
benchmark.Run(baseAddress);
benchmark = new BenchmarkService(typeof(MultipleService), threadCount, threadCalls);
benchmark.Run(baseAddress);
}
baseAddress = "http://localhost/base";
for (int i = 0; i <= 4; i++)
{
benchmark = new BenchmarkService(typeof(SingleService), threadCount, threadCalls);
benchmark.Run(baseAddress);
benchmark = new BenchmarkService(typeof(MultipleService), threadCount, threadCalls);
benchmark.Run(baseAddress);
}
Console.WriteLine("Press ENTER to close.");
Console.ReadLine();
}
}
public static class Extensions
{
static public void Shutdown(this ICommunicationObject obj)
{
try
{
if (obj != null)
obj.Close();
}
catch (Exception ex)
{
Console.WriteLine("Shutdown exception: {0}", ex.Message);
obj.Abort();
}
}
}
}
Excelente pregunta. ¿Has considerado construir esto en una aplicación de consola y probarlo para ver? –