Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El ejemplo de agrupación muestra cómo ampliar Windows Communication Foundation (WCF) para admitir la agrupación de objetos. En el ejemplo se muestra cómo crear un atributo que es sintáctica y semánticamente similar a la ObjectPoolingAttribute funcionalidad de atributo de Enterprise Services. La agrupación de objetos puede proporcionar un aumento considerable del rendimiento de una aplicación. Sin embargo, puede tener el efecto opuesto si no se usa correctamente. La agrupación de objetos ayuda a reducir la sobrecarga de volver a crear objetos usados con frecuencia que requieren una inicialización extensa. Sin embargo, si una llamada a un método en un objeto agrupado tarda una cantidad considerable de tiempo en completarse, la agrupación de objetos pone en cola solicitudes adicionales en cuanto se alcanza el tamaño máximo del grupo. Así, puede no atender algunas solicitudes de creación de objetos lanzando una excepción de tiempo de espera.
Nota:
El procedimiento de instalación y las instrucciones de compilación de este ejemplo se encuentran al final de este tema.
El primer paso para crear una extensión WCF es decidir el punto de extensibilidad que se va a usar.
En WCF, el término distribuidor hace referencia a un componente en tiempo de ejecución responsable de convertir los mensajes entrantes en invocaciones de método en el servicio del usuario y para convertir valores devueltos de ese método a un mensaje saliente. Un servicio WCF crea un distribuidor para cada punto de conexión. Un cliente WCF debe usar un despachador si el contrato asociado a ese cliente es un contrato dúplex.
Los distribuidores de extremos y canales proporcionan extensibilidad para canales y contratos exponiendo varias propiedades que controlan el comportamiento del distribuidor. La DispatchRuntime propiedad también permite inspeccionar, modificar o personalizar el proceso de distribución. Este ejemplo se centra en la InstanceProvider propiedad que apunta al objeto que proporciona las instancias de la clase de servicio.
IInstanceProvider
En WCF, el distribuidor crea instancias de la clase de servicio mediante el uso de un InstanceProvider, que implementa la interfaz IInstanceProvider. Esta interfaz tiene tres métodos:
GetInstance(InstanceContext, Message): cuando llega un mensaje, el distribuidor llama al GetInstance(InstanceContext, Message) método para crear una instancia de la clase de servicio para procesar el mensaje. La frecuencia de las llamadas a este método está determinada por la propiedad InstanceContextMode. Por ejemplo, si se establece la propiedad InstanceContextMode en PerCall, se crea una nueva instancia de la clase de servicio para procesar cada mensaje que llega, por lo que GetInstance(InstanceContext, Message) se llama cada vez que llega un mensaje.
GetInstance(InstanceContext): es idéntico al método anterior, salvo que se invoca cuando no hay ningún argumento Message.
ReleaseInstance(InstanceContext, Object): Cuando ha transcurrido la duración de una instancia de servicio, el distribuidor llama al método ReleaseInstance(InstanceContext, Object). Al igual que para el GetInstance(InstanceContext, Message) método , la frecuencia de las llamadas a este método viene determinada por la InstanceContextMode propiedad .
Pool de objetos
Una implementación personalizada IInstanceProvider proporciona la semántica de agrupación de objetos necesaria para un servicio. Por consiguiente, este ejemplo tiene un tipo ObjectPoolingInstanceProvider que proporciona implementación personalizada de IInstanceProvider para la agrupación. Cuando Dispatcher llama al método GetInstance(InstanceContext, Message), en lugar de crear una nueva instancia, la implementación personalizada busca un objeto existente en un almacén de memoria. Si hay uno disponible, se devuelve. De lo contrario, se crea un nuevo objeto. La implementación de GetInstance se muestra en el código de ejemplo siguiente.
object IInstanceProvider.GetInstance(InstanceContext instanceContext, Message message)
{
object obj = null;
lock (poolLock)
{
if (pool.Count > 0)
{
obj = pool.Pop();
}
else
{
obj = CreateNewPoolObject();
}
activeObjectsCount++;
}
WritePoolMessage(ResourceHelper.GetString("MsgNewObject"));
idleTimer.Stop();
return obj;
}
La implementación ReleaseInstance personalizada agrega la instancia liberada de nuevo al grupo y disminuye el valor de ActiveObjectsCount.
Dispatcher puede llamar a estos métodos desde subprocesos diferentes, por lo que se necesita tener acceso sincronizado a los miembros del nivel de clase en la clase ObjectPoolingInstanceProvider.
void IInstanceProvider.ReleaseInstance(InstanceContext instanceContext, object instance)
{
lock (poolLock)
{
pool.Push(instance);
activeObjectsCount--;
WritePoolMessage(
ResourceHelper.GetString("MsgObjectPooled"));
// When the service goes completely idle (no requests
// are being processed), the idle timer is started
if (activeObjectsCount == 0)
idleTimer.Start();
}
}
El ReleaseInstance método proporciona una función de limpieza durante la inicialización. Normalmente el grupo mantiene un número mínimo de objetos para la duración del grupo. Sin embargo, puede haber períodos de uso excesivo requiriendo la creación de objetos adicionales en el pool para alcanzar el límite máximo especificado en la configuración. Finalmente, cuando la piscina se vuelve menos activa, esos objetos sobrantes pueden convertirse en una sobrecarga adicional. Por lo tanto, cuando el activeObjectsCount alcanza cero, se inicia un temporizador de inactividad que desencadena y realiza un ciclo de limpieza.
Adición del comportamiento
Las extensiones de nivel de distribuidor se enlazan utilizando los comportamientos siguientes:
Comportamientos de servicio. Esto permite la personalización de todo el tiempo de ejecución del servicio.
Comportamientos de punto de conexión. Permiten la personalización de los puntos de conexión del servicio, específicamente un distribuidor de canales y de puntos de conexión.
Comportamientos del contrato. Esto permite la personalización de las ClientRuntime clases y DispatchRuntime en el cliente y el servicio, respectivamente.
Para crear una extensión de agrupación de objetos, se debe crear un comportamiento de servicio. Los comportamientos de servicio se crean mediante la implementación de la IServiceBehavior interfaz . Hay varias maneras de hacer que el modelo de servicio sea consciente de los comportamientos personalizados:
Uso de un atributo personalizado.
Agregarlo de manera imperativa a la colección de comportamientos de la descripción del servicio.
Extensión del archivo de configuración.
En este ejemplo se usa un atributo personalizado. ServiceHost Cuando se construye, examina los atributos usados en la definición de tipo del servicio y agrega los comportamientos disponibles a la colección de comportamientos de la descripción del servicio.
La interfaz IServiceBehavior tiene tres métodos en él: Validate, AddBindingParametersy ApplyDispatchBehavior. El Validate método se usa para asegurarse de que el comportamiento se puede aplicar al servicio. En este ejemplo, la implementación garantiza que el servicio no esté configurado con Single. El AddBindingParameters método se usa para configurar los enlaces del servicio. No es necesario en este escenario. ApplyDispatchBehavior se usa para configurar los gestores del servicio. WCF llama a este método cuando se inicializa ServiceHost. Los parámetros siguientes se pasan a este método:
Description: este argumento proporciona la descripción del servicio para todo el servicio. Esto se puede usar para inspeccionar los datos de descripción sobre los puntos de conexión, contratos, enlaces y otros datos del servicio.ServiceHostBase: Este argumento proporciona el ServiceHostBase que se está inicializando actualmente.
En la implementación personalizada IServiceBehavior, se crea una nueva instancia de ObjectPoolingInstanceProvider y se asigna a la propiedad InstanceProvider en cada DispatchRuntime en ServiceHostBase.
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
// Create an instance of the ObjectPoolInstanceProvider.
ObjectPoolingInstanceProvider instanceProvider = new
ObjectPoolingInstanceProvider(description.ServiceType,
minPoolSize);
// Forward the call if we created a ServiceThrottlingBehavior.
if (this.throttlingBehavior != null)
{
((IServiceBehavior)this.throttlingBehavior).ApplyDispatchBehavior(description, serviceHostBase);
}
// In case there was already a ServiceThrottlingBehavior
// (this.throttlingBehavior==null), it should have initialized
// a single ServiceThrottle on all ChannelDispatchers.
// As we loop through the ChannelDispatchers, we verify that
// and modify the ServiceThrottle to guard MaxPoolSize.
ServiceThrottle throttle = null;
foreach (ChannelDispatcherBase cdb in
serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
// Make sure there is exactly one throttle used by all
// endpoints. If there were others, we could not enforce
// MaxPoolSize.
if ((this.throttlingBehavior == null) &&
(this.maxPoolSize != Int32.MaxValue))
{
throttle ??= cd.ServiceThrottle;
if (cd.ServiceThrottle == null)
{
throw new
InvalidOperationException(ResourceHelper.GetString("ExNullThrottle"));
}
if (throttle != cd.ServiceThrottle)
{
throw new InvalidOperationException(ResourceHelper.GetString("ExDifferentThrottle"));
}
}
foreach (EndpointDispatcher ed in cd.Endpoints)
{
// Assign it to DispatchBehavior in each endpoint.
ed.DispatchRuntime.InstanceProvider =
instanceProvider;
}
}
}
// Set the MaxConcurrentInstances to limit the number of items
// that will ever be requested from the pool.
if ((throttle != null) && (throttle.MaxConcurrentInstances >
this.maxPoolSize))
{
throttle.MaxConcurrentInstances = this.maxPoolSize;
}
}
Además de una IServiceBehavior implementación, la ObjectPoolingAttribute clase tiene varios miembros para personalizar el grupo de objetos mediante los argumentos de atributo. Estos miembros incluyen MaxPoolSize, MinPoolSizey CreationTimeout, para que coincidan con el conjunto de características de agrupación de objetos proporcionado por los servicios de .NET Enterprise.
El comportamiento de agrupación de objetos ahora se puede agregar a un servicio WCF anotando la implementación del servicio con el atributo personalizado ObjectPooling recién creado.
[ObjectPooling(MaxPoolSize=1024, MinPoolSize=10, CreationTimeout=30000)]
public class PoolService : IPoolService
{
// …
}
Ejecución del ejemplo
En el ejemplo se muestran las ventajas de rendimiento que se pueden obtener mediante la agrupación de objetos en determinados escenarios.
La aplicación de servicio implementa dos servicios: WorkService y ObjectPooledWorkService. Ambos servicios comparten la misma implementación: ambos requieren una inicialización costosa y, a continuación, exponen un DoWork() método relativamente barato. La única diferencia es que tiene ObjectPooledWorkService la agrupación de objetos configurada:
[ObjectPooling(MinPoolSize = 0, MaxPoolSize = 5)]
public class ObjectPooledWorkService : IDoWork
{
public ObjectPooledWorkService()
{
Thread.Sleep(5000);
ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService instance created.");
}
public void DoWork()
{
ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService.GetData() completed.");
}
}
Al ejecutar el cliente, sincroniza llamando a WorkService cinco veces. Después, sincroniza llamando a ObjectPooledWorkService cinco veces. A continuación, se muestra la diferencia en el tiempo:
Press <ENTER> to start the client.
Calling WorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling WorkService took: 26722 ms.
Calling ObjectPooledWorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling ObjectPooledWorkService took: 5323 ms.
Press <ENTER> to exit.
Nota:
La primera vez que el cliente ejecuta ambos servicios parecen tardar aproximadamente la misma cantidad de tiempo. Si vuelve a ejecutar el ejemplo, puede ver que ObjectPooledWorkService devuelve mucho más rápido porque ya existe una instancia de ese objeto en el grupo.
Para configurar, compilar y ejecutar el ejemplo
Asegúrese de que ha realizado el procedimiento de instalación única para los ejemplos de Windows Communication Foundation.
Para compilar la solución, siga las instrucciones que se indican en Compilación de los ejemplos de Windows Communication Foundation.
Para ejecutar el ejemplo en una configuración de una sola máquina o de varias máquinas, siga las instrucciones que se indican en Ejecución de los ejemplos de Windows Communication Foundation.
Nota:
Si usa Svcutil.exe para volver a generar la configuración de este ejemplo, asegúrese de modificar el nombre del punto de conexión en la configuración de cliente para que coincida con el código de cliente.