Compartilhar via


Tempo de vida personalizado

O exemplo de tempo de vida demonstra como gravar uma extensão do WCF (Windows Communication Foundation) para fornecer serviços de tempo de vida personalizados para instâncias de serviço WCF compartilhadas.

Observação

O procedimento de instalação e as instruções de compilação desse exemplo estão localizadas no final deste artigo.

Instanciação compartilhada

O WCF oferece vários modos de instanciação para suas instâncias de serviço. O modo de instanciação compartilhada descrito neste artigo oferece uma forma de compartilhar uma instância de serviço entre vários canais. Os clientes podem entrar em contato com um método de fábrica no serviço e criar um novo canal para iniciar a comunicação. O snippet de código a seguir mostra como um aplicativo do cliente cria um novo canal para uma instância de serviço existente:

// Create a header for the shared instance id
MessageHeader shareableInstanceContextHeader = MessageHeader.CreateHeader(
        CustomHeader.HeaderName,
        CustomHeader.HeaderNamespace,
        Guid.NewGuid().ToString());

// Create the channel factory
ChannelFactory<IEchoService> channelFactory =
    new ChannelFactory<IEchoService>("echoservice");

// Create the first channel
IEchoService proxy = channelFactory.CreateChannel();

// Call an operation to create shared service instance
using (new OperationContextScope((IClientChannel)proxy))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
    Console.WriteLine("Service returned: " + proxy.Echo("Apple"));
}

((IChannel)proxy).Close();

// Create the second channel
IEchoService proxy2 = channelFactory.CreateChannel();

// Call an operation using the same header that will reuse the shared service instance
using (new OperationContextScope((IClientChannel)proxy2))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
    Console.WriteLine("Service returned: " + proxy2.Echo("Apple"));
}

Ao contrário de outros modos de instanciação, o modo de instanciação compartilhada tem uma forma exclusiva de liberar as instâncias de serviço. Por padrão, quando todos os canais são fechados para InstanceContext, o runtime do serviço WCF verifica se o serviço InstanceContextMode está configurado para PerCall ou PerSessione, se assim for, libera a instância e reivindica os recursos. Se um IInstanceContextProvider personalizado estiver sendo usado, o WCF chama o método IsIdle da implementação do provedor antes de liberar a instância. Se IsIdle retornar true, a instância é liberada, caso contrário, a implementação IInstanceContextProvider será responsável por notificar o estado ocioso do Dispatcher usando um método de retorno de chamada. Essa ação é feita chamando o método NotifyIdle do provedor.

Este exemplo mostra como você pode atrasar a liberação de InstanceContext com um tempo limite ocioso de 20 segundos.

Estendendo o InstanceContext

No WCF, InstanceContext é o link entre a instância de serviço e Dispatcher. O WCF permite estender esse componente de runtime adicionando um novo estado ou comportamento que usa seu padrão de objeto extensível. O padrão de objeto extensível é usado no WCF para estender classes de runtime existentes com nova funcionalidade ou adicionar novos recursos de estado a um objeto. Há três interfaces no padrão de objeto extensível: IExtensibleObject<T>, IExtension<T>e IExtensionCollection<T>.

A interface IExtensibleObject<T> é implementada por objetos para permitir extensões que personalizam sua funcionalidade.

A interface IExtension<T> é implementada por objetos que podem ser extensões de classes do tipo T.

E, por fim, a interface IExtensionCollection<T> é uma coleção de implementações IExtension<T> que permite a recuperação de uma implementação de IExtension<T> pelo seu tipo.

Portanto, para estender a interface InstanceContext, você deve implementar a interface IExtension<T>. Neste projeto de exemplo, a classe CustomLeaseExtension contém essa implementação.

class CustomLeaseExtension : IExtension<InstanceContext>
{
}

A interface IExtension<T> tem dois métodos Attach e Detach. Como sugerem seus nomes, esses dois métodos são chamados quando o runtime anexa e desanexa a extensão a uma instância da classe InstanceContext. Neste exemplo, o método Attach é usado para controlar o objeto InstanceContext que pertence à instância atual da extensão.

InstanceContext owner;

public void Attach(InstanceContext owner)
{
    this.owner = owner;
}

Além disso, você deve adicionar a implementação necessária à extensão para oferecer o suporte de tempo de vida estendido. Portanto, a interface ICustomLease é declarada com os métodos desejados e implementada na classe CustomLeaseExtension.

interface ICustomLease
{
    bool IsIdle { get; }
    InstanceContextIdleCallback Callback { get; set; }
}

class CustomLeaseExtension : IExtension<InstanceContext>, ICustomLease
{
}

Quando o WCF chama o método IsIdle na implementação IInstanceContextProvider, essa chamada é encaminhada para o método IsIdle do CustomLeaseExtension. Em seguida, CustomLeaseExtension verifica o estado particular para confirmar se InstanceContext está ocioso. Se estiver ocioso, retorna true. Caso contrário, inicia um temporizador para uma quantidade específica de tempo de vida estendido.

public bool IsIdle
{
  get
  {
    lock (thisLock)
    {
      if (isIdle)
      {
        return true;
      }
      else
      {
        StartTimer();
        return false;
      }
    }
  }
}

No evento Elapsed do temporizador, a função de retorno de chamada no Dispatcher é chamada para iniciar outro ciclo de limpeza.

void idleTimer_Elapsed(object sender, ElapsedEventArgs args)
{
    lock (thisLock)
    {
        StopTimer();
        isIdle = true;
        Utility.WriteMessageToConsole(
            ResourceHelper.GetString("MsgLeaseExpired"));
        callback(owner);
    }
}

Não há como renovar o temporizador de execução quando uma nova mensagem chega para a instância que é movida para o estado ocioso.

O exemplo implementa IInstanceContextProvider para interceptar as chamadas para o método IsIdle e roteá-las para CustomLeaseExtension. A implementação IInstanceContextProvider está contida na classe CustomLifetimeLease. O método IsIdle é invocado quando o WCF está prestes a liberar a instância de serviço. No entanto, há apenas uma instância de uma implementação específica ISharedSessionInstance na coleção IInstanceContextProvider de ServiceBehavior. Isso significa que não há nenhuma forma de saber se InstanceContext está fechado no momento em que o WCF verifica o método IsIdle. Portanto, este exemplo usa o bloqueio de thread para serializar solicitações para o método IsIdle.

Importante

O uso do bloqueio de thread não é um método recomendado porque a serialização pode afetar gravemente o desempenho do aplicativo.

Um campo do membro privado é usado na classe CustomLifetimeLease para acompanhar o estado ocioso e é retornado pelo método IsIdle. Cada vez que o método IsIdle é chamado, o campo isIdle retorna e é redefinido para false. É essencial definir esse valor para false a fim de garantir que o Dispatcher chame o método NotifyIdle.

public bool IsIdle(InstanceContext instanceContext)
{
    get
    {
        lock (thisLock)
        {
            //...
            bool idleCopy = isIdle;
            isIdle = false;
            return idleCopy;
        }
    }
}

Se o método IInstanceContextProvider.IsIdle retornar false, o Dispatcher registra uma função de retorno de chamada usando o método NotifyIdle. Esse método recebe uma referência ao InstanceContext depois de liberado. Portanto, o código de exemplo pode consultar a extensão de tipo ICustomLease e verificar a propriedade ICustomLease.IsIdle no estado estendido.

public void NotifyIdle(InstanceContextIdleCallback callback,
            InstanceContext instanceContext)
{
    lock (thisLock)
    {
       ICustomLease customLease =
           instanceContext.Extensions.Find<ICustomLease>();
       customLease.Callback = callback;
       isIdle = customLease.IsIdle;
       if (isIdle)
       {
             callback(instanceContext);
       }
    }
}

Antes que a propriedade ICustomLease.IsIdle seja verificada, a propriedade de retorno de chamada precisa ser definida, pois é muito importante para CustomLeaseExtension notificar o Dispatcher quando está ocioso. Se ICustomLease.IsIdle retornar true, o membro particular isIdle será simplesmente definido em CustomLifetimeLease para true e chamará o método de retorno de chamada. Como o código contém um bloqueio, outros threads não podem alterar o valor desse membro particular. E na próxima vez que o Dispatcher chamar IInstanceContextProvider.IsIdle, retorna true e permite que o Dispatcher libere a instância.

Agora que as bases para a extensão personalizada estão concluídas, ela precisa ser conectada ao modelo de serviço. Para conectar a implementação CustomLeaseExtension ao InstanceContext, o WCF fornece a interface IInstanceContextInitializer para executar a inicialização de InstanceContext. No exemplo, a classe CustomLeaseInitializer implementa essa interface e adiciona uma instância de CustomLeaseExtension à coleção Extensions a partir de uma única inicialização de método. Esse método é chamado pelo Dispatcher enquanto inicializa InstanceContext.

public void InitializeInstanceContext(InstanceContext instanceContext,
    System.ServiceModel.Channels.Message message, IContextChannel channel)

    //...

    IExtension<InstanceContext> customLeaseExtension =
        new CustomLeaseExtension(timeout, headerId);
    instanceContext.Extensions.Add(customLeaseExtension);
}

Por fim, a implementação IInstanceContextProvider é conectada ao modelo de serviço usando a implementação IServiceBehavior. Essa implementação é colocada na classe CustomLeaseTimeAttribute e também deriva da classe base Attribute para mostrar esse comportamento como um atributo.

public void ApplyDispatchBehavior(ServiceDescription description,
           ServiceHostBase serviceHostBase)
{
    CustomLifetimeLease customLease = new CustomLifetimeLease(timeout);

    foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher cd = cdb as ChannelDispatcher;

        if (cd != null)
        {
            foreach (EndpointDispatcher ed in cd.Endpoints)
            {
                ed.DispatchRuntime.InstanceContextProvider = customLease;
            }
        }
    }
}

Esse comportamento pode ser adicionado a uma classe de serviço de exemplo pela anotação com o atributo CustomLeaseTime.

[CustomLeaseTime(Timeout = 20000)]
public class EchoService : IEchoService
{
  //…
}

Quando você executa o exemplo, as solicitações de operação e as respostas são exibidas no serviço e na janela do console do cliente. Pressione ENTER em cada janela do console para desligar o serviço e o cliente.

Para configurar, compilar, e executar o exemplo

  1. Verifique se você executou o Procedimento de Instalação Única para os Exemplos do Windows Communication Foundation.

  2. Para compilar a edição C# ou do Visual Basic .NET da solução, siga as instruções descritas em Como compilar os exemplos do Windows Communication Foundation.

  3. Para executar a amostra em uma configuração de computador único ou entre computadores, siga as instruções contidas em Como executar as amostras do Windows Communication Foundation.