2012-09-01 16 views
10

Siempre pensé que establecer InstanceContextMode en PerCall hace que el modo de concurrencia sea irrelevante, incluso si usa un enlace de sesión consciente como net.tcp. Esto es lo que MSDN dice http://msdn.microsoft.com/en-us/library/ms731193.aspx "En PerCallinstancing, la concurrencia no es relevante, porque cada mensaje es procesado por un nuevo InstanceContext y, por lo tanto, nunca más de un hilo está activo en InstanceContext."¿ConcurrencyMode of Multiple tiene relevancia cuando InstanceContextMode es PerCall para un servicio WCF con enlace Net.Tcp?


Pero hoy me iba a través del libro de programación servicios WCF de Juval Lowy y escribe en el capítulo 8

Si el servicio de llamada por llamada tiene una sesión a nivel de transporte, ya sea procesamiento simultáneo de llamadas está permitido es un producto del servicio modo concurrencia. Si el servicio está configurado con ConcurrencyMode.Single, el procesamiento concurrente de las llamadas pendientes no se atenúa, y las llamadas se envían una a la vez. [...] Considero que es un diseño defectuoso. Si el servicio es configurado con ConcurrencyMode.Multiple, el procesamiento concurrente es permitido. Las llamadas se envían a medida que llegan, cada una a una nueva instancia, y se ejecutan simultáneamente. Una observación interesante aquí es que en el interés de la transacción, es una buena idea configurar un servicio por llamada con ConcurrencyMode.Multiple- la instancia en sí seguirá siendo segura para subprocesos (para que no incurra en el sincronización responsabilidad), pero permitirá llamadas simultáneas del mismo cliente.


Esto se contradice mi entendimiento y lo que dice MSDN. Cual es correcta ? En mi caso, tengo un servicio WCF Net.Tcp que usa mis muchas aplicaciones cliente que crea un nuevo objeto proxy, realiza la llamada y luego cierra el proxy inmediatamente. El servicio tiene PerCall InstanceContextMode. ¿Obtendré un mejor rendimiento si cambio InstanceContextMode a Multiple sin un peor comportamiento de seguridad de subprocesos que percall?

+0

Excelente pregunta. ¿Has considerado construir esto en una aplicación de consola y probarlo para ver? –

Respuesta

8

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(); 
      } 
     } 
    } 
} 
+0

Gracias ese tipo de confirma mis pensamientos. Al menos no hay ningún problema para configurar ConcurrencyMode en Multiple. – softveda

Cuestiones relacionadas