Compartilhar via


Noções básicas de alterações de estado

Este tópico aborda os estados e as transições que os canais têm, os tipos usados para estruturar os estados de canal e como implementá-los.

Máquinas de estado e canais

Objetos que lidam com a comunicação, por exemplo, soquetes, geralmente apresentam uma máquina de estado cujas transições de estado estão relacionadas à alocação de recursos de rede, à criação ou aceitação de conexões, ao fechamento de conexões e ao encerramento da comunicação. A máquina de estado do canal fornece um modelo uniforme dos estados de um objeto de comunicação que abstrai a implementação subjacente desse objeto. A interface ICommunicationObject fornece um conjunto de estados, métodos de transição de estado e eventos de transição de estado. Todos os canais, alocadores de canal e ouvintes de canal implementam a máquina de estado do canal.

Os eventos Closed, Closing, Faulted, Opened e Opening sinalizam um observador externo após uma transição de estado.

Os métodos Abort, Close e Open (e seus equivalentes assíncronos) causam transições de estado.

A propriedade state retorna o estado atual conforme definido por CommunicationState:

ICommunicationObject, CommunicationObject e States e State Transition

Um ICommunicationObject começa no estado Created, em que suas várias propriedades podem ser configuradas. Uma vez no estado Opened, o objeto é utilizável para enviar e receber mensagens, mas suas propriedades são consideradas imutáveis. Uma vez no estado Closing, o objeto não pode mais processar novas solicitações de envio ou recebimento, mas as solicitações existentes têm a chance de ser concluídas até que o tempo limite de Close seja atingido. Se ocorrer um erro irrecuperável, o objeto fará a transição para o estado Faulted, em que pode ser inspecionado para obter informações sobre o erro e, por fim, fechado. Quando está no estado Closed, o objeto atingiu essencialmente o fim da máquina de estado. Depois que um objeto faz a transição de um estado para outro, ele não volta para o estado anterior.

O diagrama a seguir mostra os estados ICommunicationObject e as transições de estado. As transições de estado podem ser causadas pela chamada de um dos três métodos: Abort, Open ou Close. Elas também podem ser causadas chamando outros métodos específicos da implementação. A transição para o estado Faulted pode ocorrer como resultado de erros ao abrir ou depois de abrir o objeto de comunicação.

Cada ICommunicationObject começa no estado Created. Nesse estado, um aplicativo pode configurar o objeto definindo suas propriedades. Depois que um objeto está em um estado diferente de Created, ele é considerado imutável.

Dataflow diagram of the channel state transition.
Figura 1. A máquina de estado ICommunicationObject.

O WCF (Windows Communication Foundation) fornece uma classe base abstrata chamada CommunicationObject que implementa ICommunicationObject e a máquina de estado do canal. O gráfico a seguir é um diagrama de estado modificado específico de CommunicationObject. Além da máquina de estado ICommunicationObject, ele mostra o momento em que métodos CommunicationObject adicionais são invocados.

Dataflow diagram of CommunicationObject implementation state changes. Figura 2. A implementação CommunicationObject da máquina de estado ICommunicationObject, incluindo chamadas para eventos e métodos protegidos.

Eventos ICommunicationObject

CommunicationObject expõe os cinco eventos definidos por ICommunicationObject. Esses eventos são projetados para código usando o objeto de comunicação a ser notificado sobre transições de estado. Conforme mostrado na Figura 2 acima, cada evento é acionado uma vez após o estado do objeto fazer a transição para o estado nomeado pelo evento. Os cinco eventos são do tipo EventHandler, que é definido como:

public delegate void EventHandler(object sender, EventArgs e);

Na implementação CommunicationObject, o remetente é o próprio CommunicationObject ou o que foi passado como remetente para o construtor CommunicationObject (se a sobrecarga desse construtor foi usada). O parâmetro EventArgs, e, sempre é EventArgs.Empty.

Retornos de chamada de objeto derivado

Além dos cinco eventos, CommunicationObject declara oito métodos virtuais protegidos projetados para permitir que um objeto derivado seja chamado antes e depois que ocorrem transições de estado.

Os métodos CommunicationObject.Open e CommunicationObject.Close têm três retornos de chamada associados a cada um. Por exemplo, correspondente a CommunicationObject.Open, há CommunicationObject.OnOpening, CommunicationObject.OnOpen e CommunicationObject.OnOpened. Associados a CommunicationObject.Close, há os métodos CommunicationObject.OnClose, CommunicationObject.OnClosing e CommunicationObject.OnClosed.

Da mesma forma, o método CommunicationObject.Abort tem um CommunicationObject.OnAbort correspondente.

Embora CommunicationObject.OnOpen, CommunicationObject.OnClose e CommunicationObject.OnAbort não tenham nenhuma implementação padrão, os outros retornos de chamada têm uma implementação padrão que é necessária para a correção da máquina de estado. Se você substituir esses métodos, certifique-se de chamar a implementação base ou substituí-la corretamente.

CommunicationObject.OnOpening, CommunicationObject.OnClosing e CommunicationObject.OnFaulted disparam os eventos CommunicationObject.Opening, CommunicationObject.Closing e CommunicationObject.Faulted correspondentes. CommunicationObject.OnOpened e CommunicationObject.OnClosed definem o estado do objeto como Opened e Closed, respectivamente, acionam os eventos CommunicationObject.Opened e CommunicationObject.Closed correspondentes.

Métodos de transição de estado

CommunicationObject fornece implementações de Abort, Close e Open. Ele também fornece um método Fault que causa uma transição de estado para o estado Faulted. A Figura 2 mostra a máquina de estado ICommunicationObject com cada transição rotulada pelo método que a causa (transições sem rótulo ocorrem dentro da implementação do método que causou a última transição rotulada).

Observação

Todas as implementações CommunicationObject de obtenção/definição do estado de comunicação são sincronizadas por thread.

Construtor

CommunicationObject fornece três construtores, e todos eles deixam o objeto no estado Created. Os construtores são definidos como:

O primeiro construtor é um construtor sem parâmetros que delega à sobrecarga do construtor que usa um objeto :

protected CommunicationObject() : this(new object()) { … }

O construtor que usa um objeto usa esse parâmetro como o objeto a ser bloqueado ao sincronizar o acesso ao estado do objeto de comunicação:

protected CommunicationObject(object mutex) { … }

Por fim, um terceiro construtor usa um parâmetro adicional que é usado como o argumento remetente quando eventos ICommunicationObject são acionados.

protected CommunicationObject(object mutex, object eventSender) { … }

Os dois construtores anteriores definem o remetente para isso.

Método Open

Pré-condição: State é Created.

Pós-condição: State é Opened ou Faulted. Pode gerar uma exceção.

O método Open() tentará abrir o objeto de comunicação e definir o estado como Opened. Se encontrar um erro, ele definirá o estado como Faulted.

O método primeiro verifica se o estado atual é Created. Se o estado atual for Opening ou Opened, ele gerará um InvalidOperationException. Se o estado atual for Closing ou Closed, ele gerará um CommunicationObjectAbortedException se o objeto tiver sido encerrado e, caso contrário, ObjectDisposedException. Se o estado atual for Faulted, ele gerará um CommunicationObjectFaultedException.

Em seguida, ele definirá o estado como Opening e chamará OnOpening() (que gera o evento Opening), OnOpen() e OnOpened() nessa ordem. OnOpened() define o estado como Opened e aciona o evento Opened. Se algum deles gerar uma exceção, Open() chamará Fault() e permitirá que a exceção seja gerada. O diagrama a seguir mostra o processo Open com mais detalhes.

Dataflow diagram of ICommunicationObject.Open state changes.
Substitua o método OnOpen para implementar lógica de abertura personalizada, como abrir um objeto de comunicação interna.

Método Close

Pré-condição: nenhuma.

Pós-condição: State é Closed. Pode gerar uma exceção.

O método Close() pode ser chamado em qualquer estado. Ele tenta fechar o objeto normalmente. Se um erro for encontrado, ele encerrará o objeto. O método não fará nada se o estado atual for Closing ou Closed. Caso contrário, ele definirá o estado como Closing. Se o estado original for Created, Opening ou Faulted, ele chamará Abort() (consulte o diagrama a seguir). Se o estado original for Opened, ele chamará OnClosing() (que gera o evento Closing), OnClose() e OnClosed() nessa ordem. Se algum deles gerar uma exceção, Close() chamará Abort() e permitirá que a exceção seja gerada. OnClosed() define o estado como Closed e aciona o evento Closed. O diagrama a seguir mostra o processo Close com mais detalhes.

Dataflow diagram of ICommunicationObject.Close state changes.
Substitua o método OnClose para implementar lógica de fechamento personalizada, como fechar um objeto de comunicação interna. Toda a lógica de fechamento normal que pode ficar bloqueada por um longo período (por exemplo, aguardar que o outro lado responda) deve ser implementada em OnClose() porque ele usa um parâmetro de tempo limite e porque não é chamado como parte de Abort().

Anular

Pré-condição: nenhuma.
Pós-condição: State é Closed. Pode gerar uma exceção.

O método Abort() não fará nada se o estado atual for Closed ou se o objeto tiver sido encerrado antes (por exemplo, possivelmente executando Abort() em outro thread). Caso contrário, ele define o estado como Closing e chama OnClosing() (que gera o evento Closing), OnAbort() e OnClosed() nessa ordem (não chama OnClose porque o objeto está sendo encerrado, não fechado). OnClosed() define o estado como Closed e aciona o evento Closed. Se alguma delas gerar uma exceção, ela será lançada novamente para o chamador de Abort. Implementações de OnClosing(), OnClosed() e OnAbort() não devem ser bloqueadas (por exemplo, na entrada/saída). O diagrama a seguir mostra o processo Abort com mais detalhes.

Dataflow diagram of ICommunicationObject.Abort state changes.
Substitua o método OnAbort para implementar lógica de encerramento personalizada, como encerrar um objeto de comunicação interna.

Falha

O método Fault é específico de CommunicationObject e não faz parte da interface ICommunicationObject. Ele é incluído aqui para integridade.

Pré-condição: nenhuma.

Pós-condição: State é Faulted. Pode gerar uma exceção.

O método Fault() não fará nada se o estado atual for Faulted ou Closed. Caso contrário, ele definirá o estado como Faulted e chamará OnFaulted(), o que gera o evento Faulted. Se OnFaulted gerar uma exceção, ele será relançado.

Métodos ThrowIfXxx

CommunicationObject tem três métodos protegidos que podem ser usados para gerar exceções se o objeto estiver em um estado específico.

ThrowIfDisposed gerará uma exceção se o estado for Closing, Closed ou Faulted.

ThrowIfDisposedOrImmutable gerará uma exceção se o estado não for Created.

ThrowIfDisposedOrNotOpen gerará uma exceção se o estado não for Opened.

As exceções geradas dependem do estado. A tabela a seguir mostra os estados diferentes e o tipo de exceção correspondente gerado chamando um ThrowIfXxx que gera esse estado.

Estado Abort foi chamado? Exceção
Criado N/D System.InvalidOperationException
Abertura N/D System.InvalidOperationException
Aberto N/D System.InvalidOperationException
Fechamento Yes System.ServiceModel.CommunicationObjectAbortedException
Fechamento Não System.ObjectDisposedException
Fechado Yes System.ServiceModel.CommunicationObjectAbortedException no caso de um objeto ter sido fechado por uma chamada anterior e explícita de Abort. Se você chamar Close no objeto, um System.ObjectDisposedException será gerado.
Fechado Não System.ObjectDisposedException
Com falha N/D System.ServiceModel.CommunicationObjectFaultedException

Tempos limite

Vários dos métodos discutidos usam parâmetros de tempo limite. Eles são Close, Open (determinadas sobrecargas e versões assíncronas), OnClose e OnOpen. Esses métodos foram projetados para permitir operações prolongadas (por exemplo, bloquear a entrada/saída enquanto fecha normalmente uma conexão) para que o parâmetro de tempo limite indique quanto tempo essas operações podem levar antes de serem interrompidas. Implementações de qualquer um desses métodos devem usar o valor de tempo limite fornecido para garantir que ele retorne ao chamador dentro desse tempo limite. Implementações de outros métodos que não usam um tempo limite não são projetadas para operações prolongadas e não devem ser bloqueadas na entrada/saída.

A exceção são as sobrecargas Open() e Close() que não usam um tempo limite. Elas usam um valor de tempo limite padrão fornecido pela classe derivada. CommunicationObject expõe duas propriedades abstratas protegidas denominadas DefaultCloseTimeout e DefaultOpenTimeout, definidas como:

protected abstract TimeSpan DefaultCloseTimeout { get; }

protected abstract TimeSpan DefaultOpenTimeout { get; }

Uma classe derivada implementa essas propriedades para fornecer o tempo limite padrão para as sobrecargas Open() e Close() que não usam um valor de tempo limite. Em seguida, as implementações Open() e Close() delegam para a sobrecarga que usa um tempo limite, passando a ele o valor de tempo limite padrão, por exemplo:

public void Open()

{

this.Open(this.DefaultOpenTimeout);

}

IDefaultCommunicationTimeouts

Essa interface tem quatro propriedades somente leitura para fornecer valores de tempo limite padrão para abrir, enviar, receber e fechar. Cada implementação é responsável por obter os valores padrão de qualquer maneira apropriada. Por conveniência, ChannelFactoryBase e ChannelListenerBase usam esses valores como 1 minuto cada por padrão.