Compartir a través de


Desarrollo de un grano

Antes de escribir código para implementar una clase específica, cree un nuevo proyecto de biblioteca de clases destinado a .NET Standard o .NET Core (preferido) o .NET Framework 4.6.1 o superior (si no puede usar .NET Standard o .NET Core debido a dependencias). Puede definir interfaces de grano y clases de grano en la misma biblioteca de clases o en dos proyectos separados para mejorar la separación de las interfaces de la implementación. En cualquier caso, los proyectos deben hacer referencia al Microsoft.Orleans. Sdk paquete NuGet.

Para obtener instrucciones más detalladas, consulte la sección Configuración del Proyecto de Tutorial Uno – Orleans Conceptos básicos.

Interfaces y clases granulares

Los granos interactúan entre ellos y son llamados desde el exterior mediante la invocación de métodos declarados como parte de sus respectivas interfaces de grano. Una clase de grano implementa una o varias interfaces de grano declaradas anteriormente. Todos los métodos de una interfaz de grano deben devolver un Task (para void los métodos), un Task<TResult>, o un ValueTask<TResult> (para los métodos que devuelven valores de tipo T).

A continuación se muestra un extracto del ejemplo de servicio de presencia de 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;
   }
}

Tiempo de espera de respuesta para métodos de grano

El runtime de Orleans permite aplicar un tiempo de espera de respuesta por método de grano. Si un método de grano no se completa dentro del tiempo de espera, el tiempo de ejecución lanza una TimeoutException excepción. Para imponer un tiempo de espera de respuesta, agregue el ResponseTimeoutAttribute a la definición del método de grano de la interfaz. Es fundamental agregar el atributo a la definición del método de la interfaz, no a la implementación del método en la clase grain, ya que tanto el cliente como el silo deben tener en cuenta el tiempo de espera.

Al extender la implementación de PlayerGrain anterior, en el ejemplo siguiente se muestra cómo imponer un tiempo de espera de respuesta en el método LeaveGame:

public interface IPlayerGrain : IGrainWithGuidKey
{
    Task<IGameGrain> GetCurrentGame();

    Task JoinGame(IGameGrain game);

    [ResponseTimeout("00:00:05")] // 5s timeout
    Task LeaveGame(IGameGrain game);
}

El código anterior establece un tiempo de espera de respuesta de cinco segundos en el método LeaveGame. Al salir de un juego, si la salida tarda más de cinco segundos, se lanza un TimeoutException.

Configuración del tiempo de espera de respuesta

De forma similar a los tiempos de espera de respuesta de métodos específicos individuales, puede configurar un tiempo de espera de respuesta predeterminado para todos los métodos de grano. Las llamadas a métodos específicos agota el tiempo de espera si no se recibe una respuesta dentro del período especificado. De forma predeterminada, este período se 30 segundos. Puede configurar el tiempo de espera de respuesta predeterminado:

Para obtener más información sobre cómo configurar Orleans, vea Configuración del cliente o Configuración del servidor .

Devolver valores de métodos de grano

Defina un método de grano que devuelva un valor de tipo T en una interfaz de grano como devolver un Task<T>. Para los métodos de grano no marcados con la async palabra clave , cuando el valor devuelto está disponible, normalmente se devuelve mediante la instrucción siguiente:

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

Defina un método de grain que no devuelva ningún valor (efectivamente, un método void) en una interfaz de grain, devolviendo un Task. El devuelto Task indica la ejecución asincrónica y la finalización del método . Para los métodos de grain no marcados con la palabra clave async, cuando un método "void" completa su ejecución, debe devolver el valor especial Task.CompletedTask.

public Task GrainMethod2()
{
    return Task.CompletedTask;
}

Un método de grano marcado como async devuelve el valor directamente:

public async Task<SomeType> GrainMethod3()
{
    return await GetSomeTypeAsync();
}

Un void método de grano marcado como async que no devuelve ningún valor simplemente devuelve al final de su ejecución:

public async Task GrainMethod4()
{
    return;
}

Si un método de grano recibe el valor devuelto de otra llamada de método asincrónico (a un grano o no) y no necesita realizar el control de errores para esa llamada, simplemente puede devolver lo Task que recibe de esa llamada asincrónica:

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

    return task;
}

Del mismo modo, un método de void grano puede devolver un Task devuelto por otra llamada en lugar de esperarlo.

public Task GrainMethod6()
{
    Task task = CallToAsyncAPI();
    return task;
}

ValueTask<T> se puede usar en lugar de Task<T>.

Referencias de grano

Una referencia de grano es un objeto proxy que implementa la misma interfaz de grano que la clase de grano correspondiente. Encapsula la identidad lógica (tipo y clave única) del grano de destino. Las referencias de grano se usan para realizar llamadas al grano de destino. Cada referencia de grano apunta a un único grano (una sola instancia de la clase de grano), pero puede crear varias referencias independientes al mismo grano.

Dado que una referencia de grano representa la identidad lógica del grano de destino, es independiente de la ubicación física del grano y sigue siendo válida incluso después de reiniciar el sistema completo. Puede usar referencias de grano como cualquier otro objeto .NET. Puede pasarlo a un método, usarlo como un valor devuelto de método, etc., e incluso guardarlo en el almacenamiento persistente.

Puede obtener una referencia de grano pasando la identidad de un grano al IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) método , donde T es la interfaz de grano y key es la clave única del grano dentro de su tipo.

En los ejemplos siguientes se muestra cómo obtener una referencia de grano para la IPlayerGrain interfaz definida anteriormente.

Desde dentro de una clase de grano:

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

Código de cliente Orleans.

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

Para obtener más información sobre las referencias de grano, consulte el artículo de referencia de grano .

Invocación del método Grain

El modelo de programación de Orleans se basa en programación asincrónica. Partiendo de la referencia de grano del ejemplo anterior, aquí se muestra cómo efectuar una invocación del método de grano.

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

Puede combinar dos o más Tasks. La operación de unión crea un nuevo objeto Task que se resuelve cuando se completan todos sus componentes Tasks. Este patrón es útil cuando un grano necesita iniciar varios cálculos y esperar a que se completen todos antes de continuar. Por ejemplo, un grano de front-end que genera una página web hecha de muchos elementos podría realizar varias llamadas de back-end (una para cada parte) y recibir un Task para cada resultado. El grano estaría entonces a la espera de la combinación de todos estos Tasks. Cuando el conjunto Task se resuelve, los individuales Tasks se han completado, y se han recibido todos los datos necesarios para dar formato a la página web.

Ejemplo:

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.

Propagación de errores

Cuando un método de grano lanza una excepción, Orleans propaga esa excepción a lo largo de la pila de llamadas y entre diferentes hosts cuando sea necesario. Para que funcione según lo previsto, las excepciones deben ser serializables por Orleansy los hosts que controlan la excepción deben tener el tipo de excepción disponible. Si un tipo de excepción no está disponible, Orleans inicia la excepción como una instancia de Orleans.Serialization.UnavailableExceptionFallbackException, conservando el mensaje, el tipo y el seguimiento de pila de la excepción original.

Las excepciones producidas desde métodos de grano no hacen que el grano se desactive a menos que la excepción herede de Orleans.Storage.InconsistentStateException. Las operaciones de almacenamiento lanzan InconsistentStateException cuando detectan que el estado en memoria del grano es incoherente con el estado de la base de datos. Aparte del control especial de InconsistentStateException, este comportamiento es similar a iniciar una excepción de cualquier objeto .NET: las excepciones no hacen que se destruya un objeto.

Métodos virtuales

Una clase de grano puede sobrescribir opcionalmente los métodos virtuales OnActivateAsync y OnDeactivateAsync. El Orleans tiempo de ejecución invoca estos métodos tras la activación y desactivación de cada grano de la clase. Esto proporciona a su código "grain" una oportunidad para realizar operaciones de inicialización y limpieza adicionales. Una excepción producida por OnActivateAsync produce un error en el proceso de activación.

Aunque OnActivateAsync (si se sobrescribe) siempre se llama como parte del proceso de activación de la granulación, no se garantiza que OnDeactivateAsync se llame en todas las situaciones (por ejemplo, en caso de fallo del servidor u otros eventos excepcionales). Por este motivo, las aplicaciones no deben confiar en OnDeactivateAsync para realizar operaciones críticas, como conservar los cambios de estado. Úselo solo para las operaciones de mejor esfuerzo.

Consulte también