Desenvolver um grão
Antes de escrever código para implementar uma classe de grãos, crie um novo projeto da Biblioteca de Classes direcionado ao .NET Standard ou ao .NET Core (preferencial) ou ao .NET Framework 4.6.1 ou superior (se não for possível usar o .NET Standard ou o .NET Core devido a dependências). As interfaces de granulação e as classes de grãos podem ser definidas no mesmo projeto da Biblioteca de Classes ou em dois projetos diferentes para uma melhor separação das interfaces da implementação. Em ambos os casos, os projetos precisam referenciar o Microsoft.Orleans. Sdk pacote NuGet.
Para obter instruções mais detalhadas, consulte a seção Configuração do Projeto do Tutorial Um – Orleans Básicos.
Interfaces e classes de grãos
Os grãos interagem entre si e são chamados externamente invocando métodos declarados como parte das respectivas interfaces dos grãos. Uma classe de grãos implementa uma ou mais interfaces de grão declaradas anteriormente. Todos os métodos de uma interface de grãos devem retornar um Task (para métodos void
), um Task<TResult> ou um ValueTask<TResult> (para métodos que retornam valores do tipo T
).
Veja a seguir um trecho do exemplo do Serviço de Presença do Orleans:
public interface IPlayerGrain : IGrainWithGuidKey
{
Task<IGameGrain> GetCurrentGame();
Task JoinGame(IGameGrain game);
Task LeaveGame(IGameGrain game);
}
public class PlayerGrain : Grain, IPlayerGrain
{
private IGameGrain _currentGame;
// Game the player is currently in. May be null.
public Task<IGameGrain> GetCurrentGame()
{
return Task.FromResult(_currentGame);
}
// Game grain calls this method to notify that the player has joined the game.
public Task JoinGame(IGameGrain game)
{
_currentGame = game;
Console.WriteLine(
$"Player {GetPrimaryKey()} joined game {game.GetPrimaryKey()}");
return Task.CompletedTask;
}
// Game grain calls this method to notify that the player has left the game.
public Task LeaveGame(IGameGrain game)
{
_currentGame = null;
Console.WriteLine(
$"Player {GetPrimaryKey()} left game {game.GetPrimaryKey()}");
return Task.CompletedTask;
}
}
Tempo limite de resposta para métodos de grain
O runtime Orleans permite que você imponha um tempo limite de resposta por método granular. Se um método de grão não for concluído dentro do tempo limite, o runtime gerará o TimeoutException. Para impor um tempo limite de resposta, adicione o ResponseTimeoutAttribute
à definição do método grain da interface. É muito importante que o atributo seja adicionado à definição do método de interface, não à implementação do método na classe grain, pois o cliente e o silo precisam estar cientes do tempo limite.
Estendendo a implementação de PlayerGrain
anterior, o exemplo a seguir mostra como impor um tempo limite de resposta no método LeaveGame
:
public interface IPlayerGrain : IGrainWithGuidKey
{
Task<IGameGrain> GetCurrentGame();
Task JoinGame(IGameGrain game);
[ResponseTimeout("00:00:05")] // 5s timeout
Task LeaveGame(IGameGrain game);
}
O código anterior define um tempo limite de resposta de cinco segundos no método LeaveGame
. Ao sair de um jogo, se levar mais de cinco segundos, um TimeoutException é lançado.
Configurar o tempo limite de resposta
Assim como os tempos limite de resposta do método de grão individual, você pode configurar um tempo limite de resposta padrão para todos os métodos de grãos. As chamadas para métodos de grain terão timeout se uma resposta não for recebida dentro de um período de tempo especificado. Por padrão, esse período é 30 segundos. Você pode configurar o tempo limite de resposta padrão:
- Configurando ResponseTimeout em ClientMessagingOptions, em um cliente externo.
- Configurando ResponseTimeout em SiloMessagingOptions, em um servidor.
Para obter mais informações sobre como configurar Orleans, consulte a configuração do cliente em ou a configuração do servidor em .
Retornar valores de métodos de grãos
Um método granular que retorna um valor do tipo T
é definido em uma interface de grãos como retornando um Task<T>
.
Para métodos de grão não marcados com a palavra-chave async
, quando o valor de retorno está disponível, ele geralmente é retornado por meio da seguinte instrução:
public Task<SomeType> GrainMethod1()
{
return Task.FromResult(GetSomeType());
}
Um método de grão que não retorna nenhum valor, efetivamente um método nulo, é definido em uma interface de grãos como retornando um Task
. O Task
retornado indica a execução assíncrona e a conclusão do método. Para métodos de 'grain' não marcados com a palavra-chave async
, quando um método 'void' conclui sua execução, ele precisa retornar o valor especial de Task.CompletedTask:
public Task GrainMethod2()
{
return Task.CompletedTask;
}
Um método de grão marcado como async
retorna o valor diretamente:
public async Task<SomeType> GrainMethod3()
{
return await GetSomeTypeAsync();
}
Um método de grão void
marcado como async
que não retorna nenhum valor simplesmente retorna no final de sua execução:
public async Task GrainMethod4()
{
return;
}
Se um método grain receber o valor de retorno de outra chamada de método assíncrono, seja para um grain ou não, e não precisar executar o tratamento de erro dessa chamada, ele pode simplesmente retornar o Task
que recebe dessa chamada assíncrona:
public Task<SomeType> GrainMethod5()
{
Task<SomeType> task = CallToAnotherGrain();
return task;
}
Da mesma forma, um método de granulação void
pode retornar um Task
que foi retornado a ele por outra chamada, em vez de aguardá-lo.
public Task GrainMethod6()
{
Task task = CallToAsyncAPI();
return task;
}
ValueTask<T>
pode ser usado em vez de Task<T>
.
Referências de grãos
Uma referência de grain é um objeto proxy que implementa a mesma interface de grain que a classe de grain correspondente. Ele encapsula a identidade lógica (tipo e chave exclusiva) do grão de destino. As referências de grãos são usadas para fazer chamadas para o grão de destino. Cada referência de grão é para um único grão (uma única instância da classe de grãos), mas pode-se criar várias referências independentes para o mesmo grão.
Como uma referência de grão representa a identidade lógica do grão de destino, ela é independente da localização física do grão e permanece válida mesmo após uma reinicialização completa do sistema. Os desenvolvedores podem usar referências de grãos como qualquer outro objeto .NET. Ele pode ser passado para um método, usado como um valor de retorno de método, etc., e até salvo no armazenamento persistente.
Uma referência de grão pode ser obtida passando a identidade de um grão para o método IGrainFactory.GetGrain<TGrainInterface>(Type, Guid), em que T
é a interface de grãos e key
é a chave exclusiva do grão dentro do tipo.
Veja a seguir exemplos de como obter uma referência de granulação da interface IPlayerGrain
definida acima.
De dentro de uma classe de grãos:
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);
Do código do cliente Orleans.
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Para obter mais informações sobre referências de grãos, consulte o artigo de referência de grãos .
Invocação do método grain
O modelo de programação Orleans é baseado em de programação assíncrona. Usando a referência de grain do exemplo anterior, veja como executar uma invocação de método de grain:
// Invoking a grain method asynchronously
Task joinGameTask = player.JoinGame(this);
// The await keyword effectively makes the remainder of the
// method execute asynchronously at a later point
// (upon completion of the Task being awaited) without blocking the thread.
await joinGameTask;
// The next line will execute later, after joinGameTask has completed.
players.Add(playerId);
É possível unir dois ou mais Tasks
; a operação de junção cria um novo Task
que é finalizado quando todos os seus Task
constituintes estão concluídos. Esse é um padrão útil quando um grão precisa iniciar vários cálculos e aguardar que todos eles sejam concluídos antes de prosseguir. Por exemplo, um grão de front-end que gera uma página da Web feita de muitas partes pode fazer várias chamadas de back-end, uma para cada parte e receber um Task
para cada resultado. Os grãos então aguardariam a junção de todos esses Tasks
; quando a junção Task
estiver resolvida, as Task
individuais terão sido concluídas, e todos os dados necessários para formatar a página da web estarão recebidos.
Exemplo:
List<Task> tasks = new List<Task>();
Message notification = CreateNewMessage(text);
foreach (ISubscriber subscriber in subscribers)
{
tasks.Add(subscriber.Notify(notification));
}
// WhenAll joins a collection of tasks, and returns a joined
// Task that will be resolved when all of the individual notification Tasks are resolved.
Task joinedTask = Task.WhenAll(tasks);
await joinedTask;
// Execution of the rest of the method will continue
// asynchronously after joinedTask is resolve.
Propagação de erro
Quando um método de grão gera uma exceção, Orleans propaga essa exceção pela pilha de chamadas e através de hosts, conforme necessário. Para que isso funcione conforme o esperado, as exceções devem ser serializáveis por Orleans e os hosts que estão tratando a exceção devem ter o tipo de exceção disponível. Se um tipo de exceção não estiver disponível, a exceção será gerada como uma instância de Orleans.Serialization.UnavailableExceptionFallbackException, preservando a mensagem, o tipo e o rastreamento de pilha da exceção original.
Exceções lançadas a partir de métodos de grain não fazem com que o grain seja desativado, a menos que a exceção derive de Orleans.Storage.InconsistentStateException.
InconsistentStateException
é gerada por operações de armazenamento que descobrem que o estado na memória do grão é inconsistente com o estado no banco de dados. Além do tratamento especial de InconsistentStateException
, esse comportamento é semelhante ao de lançar uma exceção de qualquer objeto .NET: exceções não fazem com que um objeto seja destruído.
Métodos virtuais
Opcionalmente, uma classe de grãos pode substituir os métodos virtuais OnActivateAsync e OnDeactivateAsync; esses métodos são invocados pelo Orleans runtime após a ativação e a desativação de cada grão da classe. Isso dá ao código granular a chance de executar operações adicionais de inicialização e limpeza. Uma exceção gerada por OnActivateAsync
falha no processo de ativação.
Embora OnActivateAsync
, se substituído, seja sempre chamado como parte do processo de ativação de grain, não há garantia de que OnDeactivateAsync
seja chamado em todas as situações, por exemplo, em caso de falha de servidor ou outro evento anormal. Por isso, os aplicativos não devem depender de OnDeactivateAsync
para executar operações críticas, como a persistência de alterações de estado. Eles devem usá-lo apenas para operações que requerem o máximo esforço.
Consulte também
- extensões Grain
- Identidade do grão
- referências de grãos
- persistência de grãos
- Visão geral do ciclo de vida do Grain
- Posicionamento do Grain