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 Tzurü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 Tasks 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.