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). Las interfaces de grano y las clases de grano se pueden definir en la misma biblioteca de clases o en dos proyectos diferentes 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 sí y se llaman desde fuera mediante la invocación de métodos declarados como parte de las interfaces de grano correspondientes. Una clase de grano implementa una o varias interfaces de grano declaradas anteriormente. Todos los métodos de una interfaz de grain deben devolver un Task (para métodos void), un Task<TResult> o un ValueTask<TResult> (para 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 el TimeoutException. Para imponer un tiempo de espera de respuesta, agregue el ResponseTimeoutAttribute a la definición del método de grano de la interfaz. Es muy importante que el atributo se agregue a la definición del método de interfaz, no a la implementación del método en la clase de grano, 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

Al igual que los tiempos de espera de respuesta de métodos de grano individuales, puede configurar un tiempo de espera de respuesta predeterminado para todos los métodos de grano. Las llamadas a métodos específicos expirarán si no se recibe una respuesta dentro de un período de tiempo 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

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

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

Un método de grain que no devuelve ningún valor, efectivamente un método void, se define en una interfaz de grain como devolver un Task. El Task devuelto indica la ejecución asincrónica y la finalización del método. Para los métodos de grano no marcados con la palabra clave async, cuando un método "void" completa su ejecución, debe devolver el valor especial de 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 grain recibe el valor devuelto de otra llamada de método asincrónico, ya sea a un grain o no, y no necesita encargarse del manejo de errores de esa llamada, simplemente puede devolver el 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 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 es a un solo 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 permanece válida incluso después de un reinicio completo del sistema. Los desarrolladores pueden usar referencias de grano como cualquier otro objeto .NET. Se puede pasar a un método, ser utilizado como valor de retorno de un método, etc., e incluso guardarse en el almacenamiento persistente.

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

A continuación se muestran ejemplos de cómo obtener una referencia de grano de la interfaz IPlayerGrain 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. Utilizando la referencia de grano del ejemplo anterior, aquí se muestra cómo invocar un 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);

Es posible unir dos o más Tasks, y la operación de combinación crea una nueva Task que se resuelve cuando se completan todas sus Taskconstituyentes. Este es un patrón ú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 esperaría entonces la unión de todos estos Tasks; cuando se resuelve la unión Task, se han completado los Taskindividuales 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 Orleans y los hosts que controlan la excepción deben tener el tipo de excepción disponible. Si un tipo de excepción no está disponible, la excepción se producirá como una instancia de Orleans.Serialization.UnavailableExceptionFallbackException, conservando el mensaje, el tipo y la traza 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. InconsistentStateException se lanza mediante operaciones de almacenamiento que detectan una incoherencia entre el estado en memoria del grain y el estado en la base de datos. Aparte de tratar InconsistentStateExceptioncomo un caso especial, este comportamiento es similar al de lanzar una excepción desde cualquier objeto .NET: las excepciones no hacen que se destruya un objeto.

Métodos virtuales

Una clase de grano puede invalidar opcionalmente los métodos virtuales OnActivateAsync y OnDeactivateAsync; el tiempo de ejecución de Orleans invoca estos métodos tras la activación y desactivación de cada grano de la clase. Esto proporciona al código de "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 invalida, siempre se llama como parte del proceso de activación de grano, no se garantiza que se llame a OnDeactivateAsync en todas las situaciones, por ejemplo, en caso de un error del servidor u otro evento anómalo. Debido a eso, las aplicaciones no deben confiar en OnDeactivateAsync para realizar operaciones críticas, como la persistencia de los cambios de estado. Solo deben usarlo para las operaciones de mejor esfuerzo.

Consulte también