Partilhar via


Desenvolver um grão

Antes de escrever código para implementar uma classe grain, crie um novo projeto de Biblioteca de Classes destinado ao .NET Standard ou .NET Core (preferencial) ou ao .NET Framework 4.6.1 ou superior (se você não puder usar o .NET Standard ou o .NET Core devido a dependências). Você pode definir interfaces de grão e classes de grão no mesmo projeto de biblioteca de classes ou em dois projetos diferentes para melhor separação de interfaces da implementação. Em ambos os casos, os projetos precisam fazer referência ao pacote NuGet Microsoft.Orleans.Sdk.

Para obter instruções mais detalhadas, consulte a seção Configuração do projeto do Tutorial Um – Orleans Noções básicas.

Interfaces e classes de grãos

Os grãos interagem uns com os outros e são chamados de fora invocando métodos declarados como parte de suas respetivas interfaces de grãos. Uma classe de grão implementa uma ou mais interfaces de grão declaradas anteriormente. Todos os métodos de uma interface de grão devem retornar um Task (para métodos void), um Task<TResult>, ou um ValueTask<TResult> (para métodos que retornam valores do tipo T).

Segue-se um excerto do Orleans exemplo de Serviço de Presença:

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 grãos

O tempo de execução Orleans permite impor um tempo limite de resposta por método de grão. Se um método grain não for concluído dentro do tempo limite, o tempo de execução lançará um TimeoutException. Para impor um tempo limite de resposta, adicione o ResponseTimeoutAttribute à definição do método grain da interface. É crucial adicionar o atributo à definição do método de interface, não à implementação do método na classe grain, pois tanto o cliente quanto o silo precisam estar cientes do tempo limite.

Estendendo a implementação anterior PlayerGrain , o exemplo a seguir mostra como impor um tempo limite de resposta no LeaveGame método:

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 LeaveGame método. Ao sair de um jogo, se demorar mais de cinco segundos, um TimeoutException é lançado.

Configurar o tempo limite de resposta

Semelhante aos tempos limite de resposta individuais dos métodos de grão, pode-se configurar um tempo limite de resposta padrão para todos os métodos de grão. As chamadas para métodos grain expiram se uma resposta não for recebida dentro do período especificado. Por padrão, esse período é de 30 segundos. Você pode configurar o tempo limite de resposta padrão:

Para obter mais informações sobre como configurar Orleanso , consulte Configuração do cliente ou Configuração do servidor.

Valores de retorno de métodos de grão

Defina um método referente a uma grain interface que retorne um valor do tipo T como Task<T>. Para métodos de grão não marcados com a palavra-chave async , quando o valor de retorno está disponível, você geralmente o retorna usando a seguinte instrução:

public Task<SomeType> GrainMethod1()
{
    return Task.FromResult(GetSomeType());
}

Defina um método de grão que não retorna nenhum valor (efetivamente um método 'void') em uma interface de grão como retornando um Task. O retornado Task indica a execução assíncrona e a conclusão do método. Para métodos de grão não marcados com a palavra-chave async , quando um método "void" conclui sua execução, ele precisa retornar o valor Task.CompletedTaskespecial:

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 void método de grão 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 (para um grain ou não) e não precisar executar o tratamento de erros para essa chamada, ele pode simplesmente retornar o que recebe dessa chamada assíncrona Task :

public Task<SomeType> GrainMethod5()
{
    Task<SomeType> task = CallToAnotherGrain();

    return task;
}

Da mesma forma, um void método de grão pode retornar um Task devolvido 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 grão é um objeto proxy que implementa a mesma interface de grão que a classe de grão correspondente. Ele encapsula a identidade lógica (tipo e chave exclusiva) do grão de destino. Você usa referências de grão para fazer chamadas para o grão de destino. Cada referência de grão aponta para um único grão (uma única instância da classe de grãos), mas você pode 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 alvo, ela é independente da localização física do grão e permanece válida mesmo após uma reinicialização completa do sistema. Você pode usar referências de grão como qualquer outro objeto .NET. Você pode passá-lo para um método, usá-lo como um valor de retorno de método, etc., e até mesmo salvá-lo no armazenamento persistente.

Você pode obter uma referência de grão passando a identidade de um grão para o IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) método, onde T é a interface de grão e key é a chave única do grão dentro de seu tipo.

Os exemplos a seguir mostram como obter uma referência de grão para a IPlayerGrain interface definida anteriormente.

De dentro de uma classe de grãos:

IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);

Do código de 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 de grãos

O Orleans modelo de programação é baseado em programação assíncrona. Usando a referência de grão do exemplo anterior, veja como você executa uma invocação de método de grão:

// 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);

Você pode juntar dois ou mais Tasks. A operação de junção cria uma nova Task que se resolve quando todos os seus constituintes Tasks são concluídos. Esse padrão é útil quando um grão precisa iniciar vários cálculos e esperar que todos eles sejam concluídos antes de prosseguir. Por exemplo, um grão de front-end que gera uma página da Web composta de muitas partes pode fazer múltiplas chamadas de back-end (uma para cada parte) e receber um Task para cada resultado. O grão aguardaria então a junção de todos estes Tasks. Quando a junção Task se resolve, o indivíduo Tasks completou e todos os dados necessários para formatar a página da Web foram 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 erros

Quando um método grain lança uma exceção, Orleans propaga essa exceção para cima da pilha de chamadas, entre hosts conforme necessário. Para que isso funcione como pretendido, as exceções devem ser serializáveis pelo Orleans, e os hosts que manipulam a exceção devem ter o tipo de exceção disponível. Se um tipo de exceção não estiver disponível, Orleans lançará a exceção como uma instância de Orleans.Serialization.UnavailableExceptionFallbackException, preservando a mensagem, o tipo e o rastreamento de pilha da exceção original.

As exceções lançadas nos métodos de grão não desativam o grão, a menos que a exceção herde de Orleans.Storage.InconsistentStateException. As operações de armazenamento são lançadas InconsistentStateException quando descobrem que o estado na memória do grão é inconsistente com o estado no banco de dados. Além da manipulação especial do InconsistentStateException, esse comportamento é semelhante ao lançamento de uma exceção de qualquer objeto .NET: as exceções não fazem com que um objeto seja destruído.

Métodos virtuais

Uma classe grain pode, opcionalmente, sobrepor os métodos OnActivateAsync e OnDeactivateAsync virtuais. O Orleans tempo de execução invoca esses métodos ao ativar e desativar cada grão da classe. Isso dá ao seu código de grão a chance de executar operações adicionais de inicialização e limpeza. Uma exceção lançada por OnActivateAsync falha no processo de ativação.

Embora OnActivateAsync (se substituído) seja sempre chamado como parte do processo de ativação de grãos, OnDeactivateAsync não é garantido que seja chamado em todas as situações (por exemplo, em caso de falha do servidor ou outros eventos anormais). Por isso, seus aplicativos não devem depender OnDeactivateAsync para executar operações críticas, como alterações de estado persistentes. Empregue-o apenas para operações de maior esforço.

Ver também