Agrupamento
O exemplo de pool demonstra como estender o Windows Communication Foundation (WCF) para oferecer suporte ao pool de objetos. O exemplo demonstra como criar um atributo que é sintaticamente e semanticamente semelhante à ObjectPoolingAttribute
funcionalidade de atributo do Enterprise Services. O pool de objetos pode fornecer um impulso dramático ao desempenho de um aplicativo. No entanto, pode ter o efeito oposto se não for usado corretamente. O pool de objetos ajuda a reduzir a sobrecarga de recriar objetos usados com freqüência que exigem inicialização extensiva. No entanto, se uma chamada para um método em um objeto agrupado levar um tempo considerável para ser concluída, o pool de objetos enfileira solicitações adicionais assim que o tamanho máximo do pool for atingido. Assim, ele pode deixar de atender a algumas solicitações de criação de objetos lançando uma exceção de tempo limite.
Nota
O procedimento de configuração e as instruções de compilação para este exemplo estão localizados no final deste tópico.
A primeira etapa na criação de uma extensão WCF é decidir o ponto de extensibilidade a ser usado.
No WCF, o termo dispatcher refere-se a um componente de tempo de execução responsável por converter mensagens de entrada em invocações de método no serviço do usuário e por converter valores de retorno desse método em uma mensagem de saída. Um serviço WCF cria um dispatcher para cada ponto de extremidade. Um cliente WCF deve usar um dispatcher se o contrato associado a esse cliente for um contrato duplex.
Os dispatchers de canal e endpoint oferecem extensibilidade em todo o canal e contrato, expondo várias propriedades que controlam o comportamento do dispatcher. A DispatchRuntime propriedade também permite inspecionar, modificar ou personalizar o processo de envio. Este exemplo se concentra na InstanceProvider propriedade que aponta para o objeto que fornece as instâncias da classe de serviço.
O IInstanceProvider
No WCF, o dispatcher cria instâncias da classe de serviço usando um InstanceProvider, que implementa a IInstanceProvider interface. Esta interface tem três métodos:
GetInstance(InstanceContext, Message): Quando uma mensagem chega, o Dispatcher chama o GetInstance(InstanceContext, Message) método para criar uma instância da classe de serviço para processar a mensagem. A frequência das chamadas para este método é determinada pela InstanceContextMode propriedade. Por exemplo, se a InstanceContextMode propriedade estiver definida como PerCall uma nova instância de classe de serviço é criada para processar cada mensagem que chega, assim GetInstance(InstanceContext, Message) é chamado sempre que uma mensagem chega.
GetInstance(InstanceContext): Isso é idêntico ao método anterior, exceto que isso é invocado quando não há nenhum argumento Message.
ReleaseInstance(InstanceContext, Object): Quando o tempo de vida de uma instância de serviço tiver decorrido, o Dispatcher chamará o ReleaseInstance(InstanceContext, Object) método. Assim como para o GetInstance(InstanceContext, Message) método, a frequência das chamadas para este método é determinada pela InstanceContextMode propriedade.
O pool de objetos
Uma implementação personalizada IInstanceProvider fornece a semântica de pool de objetos necessária para um serviço. Portanto, este exemplo tem um tipo que fornece implementação ObjectPoolingInstanceProvider
personalizada de IInstanceProvider para pooling. Quando o Dispatcher
chama o GetInstance(InstanceContext, Message) método, em vez de criar uma nova instância, a implementação personalizada procura um objeto existente em um pool na memória. Se houver um disponível, ele é devolvido. Caso contrário, um novo objeto será criado. A implementação para GetInstance
é mostrada no código de exemplo a seguir.
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;
}
A implementação personalizada ReleaseInstance
adiciona a instância liberada de volta ao pool e diminui o ActiveObjectsCount
valor. O Dispatcher
pode chamar esses métodos de diferentes threads e, portanto, o acesso sincronizado aos membros de nível de ObjectPoolingInstanceProvider
classe na classe é necessário.
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();
}
}
O ReleaseInstance
método fornece um recurso de "inicialização de limpeza". Normalmente, o pool mantém um número mínimo de objetos durante o tempo de vida do pool. No entanto, pode haver períodos de uso excessivo que exigem a criação de objetos adicionais no pool para atingir o limite máximo especificado na configuração. Eventualmente, quando o pool se torna menos ativo, esses objetos excedentes podem se tornar uma sobrecarga extra. Portanto, quando o atinge zero, é iniciado um temporizador ocioso activeObjectsCount
que aciona e executa um ciclo de limpeza.
Adicionando o comportamento
As extensões da camada de dispatcher são conectadas usando os seguintes comportamentos:
Comportamentos de serviço. Estes permitem a personalização de todo o tempo de execução do serviço.
Comportamentos de ponto final. Eles permitem a personalização de pontos de extremidade de serviço, especificamente um Channel e Endpoint Dispatcher.
Comportamentos contratuais. Estes permitem a personalização de ambos e ClientRuntimeDispatchRuntime classes no cliente e no serviço, respectivamente.
Para a finalidade de uma extensão de pool de objetos, um comportamento de serviço deve ser criado. Os comportamentos de serviço são criados implementando a IServiceBehavior interface. Há várias maneiras de tornar o modelo de serviço ciente dos comportamentos personalizados:
Usando um atributo personalizado.
Adicioná-lo imperativamente à coleção de comportamentos da descrição do serviço.
Estendendo o arquivo de configuração.
Este exemplo usa um atributo personalizado. Quando o é construído, ServiceHost ele examina os atributos usados na definição de tipo do serviço e adiciona os comportamentos disponíveis à coleção de comportamentos da descrição do serviço.
A interface IServiceBehavior tem três métodos -- Validate, AddBindingParameters, e ApplyDispatchBehavior. O Validate método é usado para garantir que o comportamento possa ser aplicado ao serviço. Neste exemplo, a implementação garante que o serviço não esteja configurado com Single. O AddBindingParameters método é usado para configurar as associações do serviço. Não é necessário neste cenário. O ApplyDispatchBehavior é usado para configurar os despachantes do serviço. Esse método é chamado pelo WCF quando o ServiceHost está sendo inicializado. Os seguintes parâmetros são passados para este método:
Description
: Este argumento fornece a descrição do serviço para todo o serviço. Isso pode ser usado para inspecionar dados de descrição sobre os pontos de extremidade, contratos, associações e outros dados do serviço.ServiceHostBase
: Este argumento fornece o ServiceHostBase que está sendo inicializado no momento.
Na implementação personalizada, IServiceBehavior uma nova instância de é instanciada ObjectPoolingInstanceProvider
e atribuída à InstanceProvider propriedade em cada DispatchRuntime um no 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;
}
}
Além de uma IServiceBehavior implementação, a ObjectPoolingAttribute classe tem vários membros para personalizar o pool de objetos usando os argumentos de atributo. Esses membros incluem MaxPoolSize, MinPoolSizee CreationTimeout, para corresponder ao conjunto de recursos de pool de objetos fornecido pelo .NET Enterprise Services.
O comportamento de pool de objetos agora pode ser adicionado a um serviço WCF anotando a implementação do serviço com o atributo personalizado ObjectPooling
recém-criado.
[ObjectPooling(MaxPoolSize=1024, MinPoolSize=10, CreationTimeout=30000)]
public class PoolService : IPoolService
{
// …
}
Executando o exemplo
O exemplo demonstra os benefícios de desempenho que podem ser obtidos usando o pool de objetos em determinados cenários.
O aplicativo de serviço implementa dois serviços -- WorkService
e ObjectPooledWorkService
. Ambos os serviços compartilham a mesma implementação -- ambos exigem inicialização cara e, em seguida, expõem um DoWork()
método que é relativamente barato. A única diferença é que o ObjectPooledWorkService
pool de objetos está configurado:
[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.");
}
}
Quando você executa o cliente, ele vezes chama o WorkService
5 vezes. Em seguida, vezes chamando as ObjectPooledWorkService
5 vezes. A diferença de tempo é então exibida:
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
Na primeira vez que o cliente é executado, ambos os serviços parecem levar aproximadamente a mesma quantidade de tempo. Se você executar novamente o exemplo, poderá ver que o ObjectPooledWorkService
retorna muito mais rápido porque uma instância desse objeto já existe no pool.
Para configurar, compilar e executar o exemplo
Certifique-se de ter executado o procedimento de instalação única para os exemplos do Windows Communication Foundation.
Para criar a solução, siga as instruções em Criando os exemplos do Windows Communication Foundation.
Para executar o exemplo em uma configuração de máquina única ou cruzada, siga as instruções em Executando os exemplos do Windows Communication Foundation.
Nota
Se você usar Svcutil.exe para regenerar a configuração para este exemplo, certifique-se de modificar o nome do ponto de extremidade na configuração do cliente para corresponder ao código do cliente.