Développer un grain

Avant d’écrire du code pour implémenter une classe de grain, créez un projet de bibliothèque de classes ciblant .NET Standard ou .Net Core (préféré) ou .NET Framework 4.6.1 ou version ultérieure (si vous ne pouvez pas utiliser .NET Standard ou .NET Core en raison de dépendances). Les interfaces de grain et les classes de grain peuvent être définies dans le même projet de bibliothèque de classes ou dans deux projets différents pour une meilleure séparation des interfaces et de l’implémentation. Dans les deux cas, les projets doivent référencer les packages NuGet Microsoft.Orleans.Core.Abstractions et Microsoft.Orleans.CodeGenerator.MSBuild .

Pour obtenir des instructions plus détaillées, consultez la section Configuration du projet du tutoriel 1 : Notions de base d’Orleans.

Interfaces et classes de grain

Les grains interagissent entre eux et sont appelés de l’extérieur en appelant des méthodes déclarées dans le cadre des interfaces de grain respectives. Une classe de grain implémente une ou plusieurs interfaces grain précédemment déclarées. Toutes les méthodes d’une interface de grain doivent retourner un Task (pour les void méthodes), un Task<TResult> ou un ValueTask<TResult> (pour les méthodes retournant des valeurs de type T).

Voici un extrait de l’exemple Orleans version 1.5 Presence Service :

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;
   }
}

Retourner des valeurs à partir des méthodes de grain

Une méthode de grain qui retourne une valeur de type T est définie dans une interface de grain comme renvoyant un Task<T>. Pour les méthodes de grain non marquées avec le async mot clé, lorsque la valeur de retour est disponible, elle est généralement retournée via l’instruction suivante :

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

Une méthode de grain qui ne retourne aucune valeur, en fait une méthode void, est définie dans une interface de grain comme renvoyant un Task. Le retourné Task indique l’exécution asynchrone et l’achèvement de la méthode. Pour les méthodes de grain non marquées avec le async mot clé, lorsqu’une méthode « void » termine son exécution, elle doit retourner la valeur spéciale de Task.CompletedTask:

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

Une méthode de grain marquée comme async retourne directement la valeur :

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

Une void méthode de grain marquée comme async qui ne retourne aucune valeur retourne simplement à la fin de son exécution :

public async Task GrainMethod4()
{
    return;
}

Si une méthode grain reçoit la valeur de retour d’un autre appel de méthode asynchrone, vers un grain ou non, et n’a pas besoin d’effectuer la gestion des erreurs de cet appel, elle peut simplement retourner le Task qu’elle reçoit de cet appel asynchrone :

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

    return task;
}

De même, une void méthode de grain peut lui retourner un Task par un autre appel au lieu de l’attendre.

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

ValueTask<T> peut être utilisé à la place de Task<T>.

Référence de grain

Une référence de grain est un objet proxy qui implémente la même interface de grain que la classe de grain correspondante. Il encapsule l’identité logique (type et clé unique) du grain cible. Une référence de grain est utilisée pour effectuer des appels au grain cible. Chaque référence de grain concerne un seul grain (un seul instance de la classe de grain), mais on peut créer plusieurs références indépendantes au même grain.

Étant donné qu’une référence de grain représente l’identité logique du grain cible, elle est indépendante de l’emplacement physique du grain et reste valide même après un redémarrage complet du système. Les développeurs peuvent utiliser des références de grain comme n’importe quel autre objet .NET. Il peut être passé à une méthode, utilisé comme valeur de retour de méthode, etc., et même enregistré dans un stockage persistant.

Une référence de grain peut être obtenue en passant l’identité d’un grain à la IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) méthode, où T est l’interface de grain et key est la clé unique du grain dans le type.

Voici des exemples illustrant comment obtenir une référence de grain de l’interface IPlayerGrain définie ci-dessus.

À partir d’une classe de grain :

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

À partir du code client Orleans.

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

Appel de méthode grain

Le modèle de programmation Orleans est basé sur la programmation asynchrone. À l’aide de la référence de grain de l’exemple précédent, voici comment effectuer un appel de méthode 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);

Il est possible de joindre deux ou plusieurs Tasks; l’opération de jointure en crée un Task qui est résolu lorsque tous ses composants Tasksont terminés. Il s’agit d’un modèle utile lorsqu’un grain doit démarrer plusieurs calculs et attendre qu’ils se terminent tous avant de continuer. Par exemple, un grain frontal qui génère une page web composée de plusieurs parties peut effectuer plusieurs appels back-end, un pour chaque partie, et recevoir un Task pour chaque résultat. Le grain attend ensuite la jointure de tous ces Taskséléments ; lorsque la jointure Task est résolue, les Taskdonnées individuelles sont terminées et toutes les données requises pour mettre en forme la page web ont été reçues.

Exemple :

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.

Méthodes virtuelles

Une classe de grain peut éventuellement remplacer OnActivateAsync les méthodes virtuelles et OnDeactivateAsync ; celles-ci sont appelées par le runtime Orleans lors de l’activation et de la désactivation de chaque grain de la classe. Cela permet au code de grain d’effectuer des opérations d’initialisation et de nettoyage supplémentaires. Une exception levée par OnActivateAsync échoue le processus d’activation. Bien que OnActivateAsync, en cas de substitution, soit toujours appelé dans le cadre du processus d’activation du grain, OnDeactivateAsync il n’est pas garanti d’être appelé dans toutes les situations, par exemple, en cas de défaillance du serveur ou d’autre événement anormal. Pour cette raison, les applications ne doivent pas s’appuyer sur OnDeactivateAsync pour effectuer des opérations critiques telles que la persistance des changements d’état. Ils ne doivent l’utiliser que pour les opérations au meilleur effort.