Dela via


Utveckla ett korn

Innan du skriver kod för att implementera en kornklass skapar du ett nytt klassbiblioteksprojekt som riktar sig till .NET Standard eller .NET Core (rekommenderas) eller .NET Framework 4.6.1 eller senare (om du inte kan använda .NET Standard eller .NET Core på grund av beroenden). Korngränssnitt och kornklasser kan definieras i samma klassbiblioteksprojekt, eller i två olika projekt för bättre separation av gränssnitt från implementeringen. I båda fallen måste projekten referera till Microsoft.Orleans. Sdk NuGet-paket.

Mer detaljerade instruktioner finns i avsnittet Project Setup i Tutorial One – Orleans Basics.

Korngränssnitt och klasser

Korn interagerar med varandra och anropas utifrån genom att anropa metoder som deklarerats som en del av respektive korngränssnitt. En kornklass implementerar ett eller flera tidigare deklarerade korngränssnitt. Alla metoder i ett korngränssnitt måste returnera en Task (för void metoder), en Task<TResult> eller en ValueTask<TResult> (för metoder som returnerar värden av typen T).

Följande är ett utdrag från exemplet Orleans 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;
   }
}

Tidsgräns för svar för kornmetoder

Med Orleans körningsmiljö kan du fastställa en tidsgräns för svar per kornmetod. Om en kornmetod inte slutförs inom tidsgränsen genererar körningen TimeoutException. Om du vill införa en tidsgräns för svar lägger du till ResponseTimeoutAttribute i gränssnittets definition av kornmetod. Det är mycket viktigt att attributet läggs till i gränssnittsmetoddefinitionen, inte till metodimplementeringen i kornklassen, eftersom både klienten och silon måste vara medvetna om tidsgränsen.

Om du utökar den tidigare PlayerGrain implementeringen visar följande exempel hur du tillämpar en tidsgräns för svar på LeaveGame-metoden:

public interface IPlayerGrain : IGrainWithGuidKey
{
    Task<IGameGrain> GetCurrentGame();

    Task JoinGame(IGameGrain game);

    [ResponseTimeout("00:00:05")] // 5s timeout
    Task LeaveGame(IGameGrain game);
}

Föregående kod anger en tidsgräns för svar på fem sekunder för metoden LeaveGame. När du lämnar ett spel, om det dröjer mer än fem sekunder skickas en TimeoutException.

Konfigurera tidsgräns för svar

Precis som timeouter för svar på enskilda kornmetoder kan du konfigurera en standardtidsgräns för svar för alla kornmetoder. Anrop till grainmetoder kommer att få tidsgräns om ett svar inte tas emot inom en angiven tidsperiod. Som standard är den här perioden 30 sekunder. Du kan konfigurera standardtidsgränsen för svar:

Mer information om hur du konfigurerar Orleansfinns i Klientkonfiguration eller Server-konfiguration.

Returnera värden från kornmetoder

En kornmetod som returnerar ett värde av typen T definieras i ett korngränssnitt som returnerar en Task<T>. För kornmetoder som inte har markerats med nyckelordet async returneras det vanligtvis via följande instruktion när returvärdet är tillgängligt:

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

En kornmetod som inte returnerar något värde, i praktiken en void-metod, definieras i ett korngränssnitt som returnerar en Task. Den returnerade Task indikerar att metoden körs och avslutas asynkront. För metoder som hanterar korn och inte har markerats med nyckelordet async, behöver ett specialvärde på Task.CompletedTaskreturneras när en "void"-metod slutför sin körning.

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

En kornmetod markerad som async returnerar värdet direkt:

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

En void kornmetod markerad som async som inte returnerar något värde returnerar helt enkelt i slutet av körningen:

public async Task GrainMethod4()
{
    return;
}

Om en kornmetod tar emot returvärdet från ett annat asynkront metodanrop, oavsett om det är till ett korn eller inte, och inte behöver utföra felhantering av det anropet, kan den enkelt returnera det Task som den mottar från detta asynkrona anrop.

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

    return task;
}

På samma sätt kan en void kornmetod returnera ett Task som den har mottagit från ett annat anrop utan att vänta på den.

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

ValueTask<T> kan användas i stället för Task<T>.

Kornreferenser

En kornreferens är ett proxyobjekt som implementerar samma korngränssnitt som motsvarande kornklass. Den kapslar in målkornets logiska identitet (typ och unik nyckel). Kornreferenser används för att göra anrop till målkornet. Varje kornreferens är till ett enda korn (en enskild instans av kornklassen), men man kan skapa flera oberoende referenser till samma korn.

Eftersom en kornreferens representerar målkornets logiska identitet är den oberoende av kornets fysiska plats och förblir giltig även efter en fullständig omstart av systemet. Utvecklare kan använda kornreferenser som andra .NET-objekt. Den kan skickas till en metod, användas som ett metodreturvärde osv., och till och med sparas i beständig lagring.

Du kan hämta en kornreferens genom att skicka identiteten för ett korn till metoden IGrainFactory.GetGrain<TGrainInterface>(Type, Guid), där T är korngränssnittet och key är den unika nyckeln för kornet inom typen.

Följande är exempel på hur du hämtar en kornreferens för det IPlayerGrain gränssnitt som definierats ovan.

Från insidan av en lektion om spannmål

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

Från Orleans klientkoden.

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

För mer information om kornreferenser, se artikeln kornreferens.

Anrop av Grain-metod

Programmeringsmodellen Orleans baseras på asynkron programmering. Med hjälp av kornreferensen från föregående exempel gör du så här för att utföra ett anrop av kornmetod:

// 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);

Det är möjligt att ansluta två eller flera Tasks; sammanslagningsoperationen skapar en ny Task som löses när alla dess beståndsdelar Tasks har slutförts. Det här är ett användbart mönster när ett korn måste starta flera beräkningar och vänta tills alla har slutförts innan du fortsätter. Till exempel kan ett frontend-korn som genererar en webbsida som består av många delar göra flera backend-anrop, en för varje del, och få en Task för varje resultat. Kornet skulle sedan invänta sammanfogningen av alla dessa Tasks; När sammanfogningen Task har lösts, har de enskilda Taskslutförts och alla de data som krävs för att formatera webbsidan har tagits emot.

Exempel:

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.

Felspridning

När en kornmetod utlöser ett undantag sprider Orleans undantaget upp i den anropande stacken över värdar efter behov. För att detta ska fungera som avsett måste undantag vara serialiserbara av Orleans och värdar som hanterar undantaget måste ha undantagstypen tillgänglig. Om en undantagstyp inte är tillgänglig genereras undantaget som en instans av Orleans.Serialization.UnavailableExceptionFallbackException, vilket bevarar meddelandet, typen och stackspårningen av det ursprungliga undantaget.

Undantag som genereras från kornmetoder gör inte att kornet inaktiveras om inte undantaget ärver från Orleans.Storage.InconsistentStateException. InconsistentStateException genereras av lagringsoperationer som upptäcker att objektets minnesinterna tillstånd är oöverensstämmande med tillståndet i databasen. Förutom specialhöljet för InconsistentStateExceptionliknar det här beteendet att utlösa ett undantag från alla .NET-objekt: undantag orsakar inte att ett objekt förstörs.

Virtuella metoder

En kornklass kan eventuellt åsidosätta de virtuella metoderna OnActivateAsync och OnDeactivateAsync. dessa metoder anropas av Orleans-körningen vid aktivering och inaktivering av varje korn i klassen. Detta ger kornkoden en chans att utföra ytterligare initierings- och rensningsåtgärder. Ett undantag som genereras av OnActivateAsync förhindrar aktiveringsprocessen.

Även om OnActivateAsync, om det åsidosätts, alltid anropas som en del av kornaktiveringsprocessen, är OnDeactivateAsync inte garanterat att anropas i alla situationer, till exempel i händelse av ett serverfel eller annan onormal händelse. Därför bör program inte förlita sig på OnDeactivateAsync för att utföra kritiska åtgärder, till exempel beständighet för tillståndsändringar. De bör endast använda den för insatser med bästa möjliga ansträngning.

Se även