Entwickeln eines Korn
Erstellen Sie vor dem Schreiben von Code zum Implementieren einer Grain-Klasse ein neues Klassenbibliotheksprojekt für .NET Standard oder .NET Core (bevorzugt) oder .NET Framework Version 4.6.1 oder höher (wenn Sie .NET Standard oder .NET Core aufgrund von Abhängigkeiten nicht verwenden können). Grain-Schnittstellen und Grain-Klassen können im selben Klassenbibliotheksprojekt oder in zwei verschiedenen Projekten definiert werden, um Schnittstellen von der Implementierung besser zu trennen. In beiden Fällen müssen die Projekte auf die NuGet-Pakete Microsoft.Orleans.Core.Abstractions und Microsoft.Orleans.CodeGenerator.MSBuild verweisen.
Ausführlichere Anweisungen finden Sie im Abschnitt Project Setup im Tutorial 1 – Orleans Basics.
Grain-Schnittstellen und -Klassen
Grains interagieren miteinander und werden von außen aufgerufen, indem Methoden aufgerufen werden, die als Teil der jeweiligen Kornschnittstellen deklariert sind. Eine Grain-Klasse implementiert eine oder mehrere zuvor deklarierte Grain-Schnittstellen. Alle Methoden einer Grain-Schnittstelle müssen ein Task (für void
-Methoden), ein Task<TResult> oder ein ValueTask<TResult> zurückgeben (für Methoden, die Werte des Typs T
zurückgeben).
Im Folgenden ist ein Auszug aus dem Beispiel für Orleans Version 1.5 Presence Service aufgeführt:
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;
}
}
Zurückgeben von Werten aus Grain-Methoden
Eine Grain-Methode, die einen Wert vom Typ T
zurückgibt, wird in einer Grain-Schnittstelle so definiert, als würde sie Task<T>
zurückgeben.
Bei Grain-Methoden, die nicht mit dem Schlüsselwort async
gekennzeichnet sind, wird der Rückgabewert normalerweise über die folgende Anweisung zurückgegeben, wenn der Rückgabewert verfügbar ist:
public Task<SomeType> GrainMethod1()
{
return Task.FromResult(GetSomeType());
}
Eine Grain-Methode, die keinen Wert zurückgibt, also eine ungültige Methode, wird in einer Grain-Schnittstelle so definiert, als würde sie Task
zurückgeben. Die zurückgegebene Task
gibt die asynchrone Ausführung und Vervollständigung der Methode an. Für Grain-Methoden, die nicht mit dem Schlüsselwort async
gekennzeichnet sind, muss der besondere Wert Task.CompletedTask zurückgegeben werden, wenn eine „ungültige“ Methode ihre Ausführung abgeschlossen hat:
public Task GrainMethod2()
{
return Task.CompletedTask;
}
Eine als async
markierte Grain-Methode gibt den Wert direkt zurück:
public async Task<SomeType> GrainMethod3()
{
return await GetSomeTypeAsync();
}
Eine void
Grain-Methode, die als async
gekennzeichnet ist und keinen Wert zurückgibt, kehrt einfach zum Ende ihrer Ausführung zurück:
public async Task GrainMethod4()
{
return;
}
Wenn eine Grain-Methode den Rückgabewert von einem anderen asynchronen Methodenaufruf an einen Grain empfängt oder nicht und keine Fehlerbehandlung für diesen Aufruf durchführen muss, kann sie einfach die Task
zurückgeben, die sie von diesem asynchronen Aufruf empfängt:
public Task<SomeType> GrainMethod5()
{
Task<SomeType> task = CallToAnotherGrain();
return task;
}
Auf ähnliche Weise kann eine void
Grain-Methode eine Task
zurückgeben, die durch einen anderen Aufruf zurückgegeben wird, anstatt darauf zu warten.
public Task GrainMethod6()
{
Task task = CallToAsyncAPI();
return task;
}
ValueTask<T>
kann anstelle von Task<T>
verwendet werden.
Grain-Verweis
Ein Grain-Verweis ist ein Proxyobjekt, das dieselbe Grain-Schnittstelle wie die entsprechende Grain-Klasse implementiert. Er kapselt die logische Identität (Typ und eindeutiger Schlüssel) des Ziel-Grains ein. Ein Grain-Verweis wird verwendet, um Aufrufe an das Ziel-Grain zu tätigen. Jeder Grain-Verweis ist auf ein einzelnes Grain (eine einzelne Instanz der Grain-Klasse), aber man kann mehrere unabhängige Verweise auf dasselbe Grain erstellen.
Da ein Grain-Verweis die logische Identität des Ziel-Grains darstellt, ist er unabhängig von der physischen Position des Grains und bleibt auch nach einem vollständigen Neustart des Systems gültig. Entwickler können Grain-Verweise wie jedes andere .NET-Objekt verwenden. Sie können an eine Methode übergeben, als Methodenrückgabewert usw. verwendet und sogar im persistenten Speicher gespeichert werden.
Ein Grain-Verweis kann abgerufen werden, indem die Identität eines Grains an die IGrainFactory.GetGrain<TGrainInterface>(Type, Guid)-Methode übergeben wird, wobei T
die Grain-Schnittstelle und key
der eindeutige Schlüssel des Grains innerhalb des Typs ist.
Im Folgenden finden Sie Beispiele zum Abrufen eines Grain-Verweises der oben definierten IPlayerGrain
-Schnittstelle.
Innerhalb einer Grain-Klasse:
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);
Aus Orleans-Clientcode.
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Grain-Methodenaufruf
Das Orleans-Programmiermodell basiert auf asynchroner Programmierung. Mit dem Grain-Verweis aus dem vorherigen Beispiel können Sie einen Grain-Methodenaufruf ausführen:
// 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 ist möglich, zwei oder mehr Tasks
zu verknüpfen. Der Joinvorgang erstellt eine neue Task
, die aufgelöst wird, wenn alle Bestandteile der Task
abgeschlossen sind. Dies ist ein nützliches Muster, wenn ein Grain mehrere Berechnungen starten und warten muss, bis alle abgeschlossen sind, bevor es fortfahren kann. Beispielsweise kann ein Front-End-Grain, das eine Webseite generiert, die aus vielen Teilen besteht, mehrere Back-End-Aufrufe ausführen, einen für jedes Teil, und eine Task
für jedes Ergebnis erhalten. Das Grain wartet dann auf die Verknüpfung aller dieser Tasks
. Wenn die Verknüpfungs-Task
aufgelöst wird, wurden die einzelnen Task
s abgeschlossen, und alle zum Formatieren der Webseite erforderlichen Daten wurden empfangen.
Beispiel:
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.
Virtuelle Methoden
Eine Grain-Klasse kann optional OnActivateAsync- und virtuelle OnDeactivateAsync-Methoden außer Kraft setzen. Diese werden von der Orleans-Runtime bei Aktivierung und Deaktivierung jedes Grains der Klasse aufgerufen. Dadurch erhält der Grain-Code die Möglichkeit, zusätzliche Initialisierungs- und Bereinigungsvorgänge auszuführen. Eine von ausgelöste OnActivateAsync
Ausnahme schlägt beim Aktivierungsprozess fehl.
Während OnActivateAsync
bei Überschreibung immer als Teil des Grain-Aktivierungsprozesses aufgerufen wird, ist nicht garantiert, dass OnDeactivateAsync
in allen Situationen aufgerufen wird, z. B. im Falle eines Serverfehlers oder eines anderen abnormalen Ereignisses. Aus diesem Grund sollten Anwendungen sich für die Ausführung kritischer Vorgänge wie die Persistenz von Zustandsänderungen nicht auf OnDeactivateAsync
verlassen. Es sollte nur für Best-Effort-Vorgänge verwendet werden.