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.
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;
}
}
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>
.
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);
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 Task
sont 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 Task
donné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.
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.