Compartilhar via


Pool

O exemplo de pooling demonstra como estender o WCF (Windows Communication Foundation) para dar suporte ao pool de objetos. O exemplo demonstra como criar um atributo que é sintaticamente e semanticamente semelhante à ObjectPoolingAttribute funcionalidade de atributo dos Serviços Empresariais. O pool de objetos pode fornecer um aumento dramático no desempenho de um aplicativo. No entanto, ele poderá ter o efeito oposto se não for usado corretamente. O pool de objetos ajuda a reduzir a sobrecarga de recriar objetos usados com frequência que exigem uma inicialização abrangente. No entanto, se uma chamada a um método em um objeto em pool demorar um tempo considerável para ser concluída, o pool de objetos agrupará solicitações adicionais assim que o tamanho máximo do pool for atingido. Portanto, pode falhar ao atender a algumas solicitações de criação de objeto gerando uma exceção de tempo limite.

Observação

O procedimento de instalação e as instruções de build 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 os 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 ele for duplex.

Os dispatchers de canal e ponto de extremidade oferecem extensibilidade de canal e contrato expondo diversas propriedades que controlam o comportamento do dispatcher. A DispatchRuntime propriedade também permite inspecionar, modificar ou personalizar o processo de expedição. 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 interface IInstanceProvider. Essa interface tem três métodos:

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 ObjectPoolingInstanceProvider que fornece implementação personalizada de IInstanceProvider para pooling. Quando o Dispatcher chama o método GetInstance(InstanceContext, Message), em vez de criar uma nova instância, a implementação personalizada procura um objeto existente em um pool na memória. Se um estiver disponível, ele será retornado. Caso contrário, um novo objeto será criado. A implementação de 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. Dispatcher pode chamar esses métodos de threads diferentes e, portanto, o acesso sincronizado aos membros de nível de classe na classe ObjectPoolingInstanceProvider é 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 método ReleaseInstance fornece um recurso de "inicialização de limpeza". Normalmente, o pool mantém um número mínimo de objetos durante a vida útil 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 activeObjectsCount chega a zero, um temporizador ocioso é iniciado que dispara e executa um ciclo de limpeza.

Adicionando o comportamento

As extensões da camada do dispatcher são conectadas usando os seguintes comportamentos:

  • Comportamentos de serviço. Elas permitem a personalização de todo o runtime do serviço.

  • Comportamentos de Ponto de Extremidade. Elas permitem a personalização dos pontos de extremidade de serviço, especificamente um Dispatcher de Canal e Ponto de Extremidade.

  • Comportamentos de contrato. Elas permitem a personalização tanto das classes ClientRuntime quanto das classes DispatchRuntime no cliente e no serviço, respectivamente.

Para fins 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.

  • Adicionando-o 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 ServiceHost é construído, ele examina os atributos utilizados 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 -- Validatee AddBindingParametersApplyDispatchBehavior. 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 dispatchers 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: esse argumento fornece a descrição do serviço para todo o serviço. Isso pode ser usado para inspecionar dados de descrição sobre pontos de extremidade, contratos, associações e outros dados do serviço.

  • ServiceHostBase: esse argumento fornece o ServiceHostBase que está sendo inicializado no momento.

Na implementação personalizada IServiceBehavior, uma nova instância de ObjectPoolingInstanceProvider é instanciada e atribuída à propriedade InstanceProvider em cada DispatchRuntime 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 implementação IServiceBehavior , 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 agrupamento de objetos agora pode ser adicionado a um serviço WCF ao anotar a implementação do serviço com o atributo ObjectPooling personalizado 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, exigindo uma inicialização dispendiosa e, em seguida, expondo um método DoWork() que é relativamente barato. A única diferença é que o ObjectPooledWorkService tem pool de objetos 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 liga o WorkService 5 vezes. Em seguida, ele liga o ObjectPooledWorkService 5 vezes. Em seguida, a diferença no tempo é 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.

Observação

A primeira vez que o cliente é executado, ambos os serviços parecem levar quase o mesmo tempo. Se você executar novamente o exemplo, poderá ver que o ObjectPooledWorkService retorno é muito mais rápido porque já existe uma instância desse objeto no pool.

Para configurar, compilar e executar o exemplo

  1. Verifique se você executou o Procedimento de instalação avulsa dos exemplos do Windows Communication Foundation.

  2. Para criar a solução, siga as instruções na criação dos exemplos do Windows Communication Foundation.

  3. Para executar o exemplo em uma configuração única ou entre máquinas, siga as instruções em Executando os exemplos do Windows Communication Foundation.

Observação

Se você usar Svcutil.exe para regenerar a configuração deste exemplo, modifique o nome do ponto de extremidade na configuração do cliente para corresponder ao código do cliente.