Condividi tramite


Sviluppare un grano

Prima di scrivere codice per implementare una classe granulare, creare un nuovo progetto di libreria di classi destinato a .NET Standard o .NET Core (preferito) o .NET Framework 4.6.1 o versione successiva (se non è possibile usare .NET Standard o .NET Core a causa di dipendenze). È possibile definire interfacce di granularità e classi granulari nello stesso progetto di libreria di classi o in due progetti diversi per una migliore separazione delle interfacce dall'implementazione. In entrambi i casi, i progetti devono fare riferimento al pacchetto NuGet Microsoft.Orleans.Sdk.

Per istruzioni più dettagliate, vedere la sezione Configurazione del progetto di Esercitazione uno - Orleans Nozioni di base.

Interfacce e classi granulari

I grani interagiscono tra loro e vengono chiamati dall'esterno richiamando metodi dichiarati come parte delle rispettive interfacce di granularità. Una classe granulare implementa una o più interfacce di granularità dichiarate in precedenza. Tutti i metodi di un'interfaccia di granularità devono restituire un oggetto Task (per i metodi void), un oggetto Task<TResult>, o (per i metodi che restituiscono valori di tipo ValueTask<TResult>) T.

Di seguito è riportato un estratto dell'esempio di Orleans Servizio presenza:

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

Timeout della risposta per i metodi granulari

Il Orleans runtime consente di applicare un timeout di risposta per ogni metodo di grain. Se un metodo grain non viene completato entro il timeout, il runtime lancia un'eccezione TimeoutException. Per imporre un timeout di risposta, aggiungere il ResponseTimeoutAttribute nella definizione del metodo grain dell'interfaccia. È fondamentale aggiungere l'attributo alla definizione del metodo di interfaccia, non all'implementazione del metodo nella classe grain, perché sia il client che il silo devono essere consapevoli del timeout.

Estendendo l'implementazione precedente PlayerGrain , nell'esempio seguente viene illustrato come imporre un timeout di risposta al LeaveGame metodo :

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

    Task JoinGame(IGameGrain game);

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

Il codice precedente imposta un timeout di risposta di cinque secondi nel LeaveGame metodo . Quando si lascia un gioco, se sono necessari più di cinque secondi, un errore TimeoutException viene lanciato.

Configurare il timeout della risposta

Analogamente ai singoli timeout di risposta per i metodi di granello, è possibile configurare un timeout di risposta predefinito per tutti i metodi di granello. Timeout delle chiamate ai metodi granulari se una risposta non viene ricevuta entro il periodo specificato. Per impostazione predefinita, questo periodo è di 30 secondi. È possibile configurare il timeout di risposta predefinito:

Per altre informazioni sulla configurazione di Orleans, vedere Configurazione client o Configurazione del server.

Restituire valori dai metodi di granularità

Definire un metodo di granularità che restituisce un valore di tipo T in un'interfaccia di granularità come restituzione di un oggetto Task<T>. Per i metodi granulari non contrassegnati con la async parola chiave , quando il valore restituito è disponibile, in genere viene restituito usando l'istruzione seguente:

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

Definire un metodo del grain che non restituisca alcun valore, ovvero un metodo void, in un'interfaccia del grain che restituisca Task. L'oggetto restituito Task indica l'esecuzione asincrona e il completamento del metodo. Per i metodi granulari non contrassegnati con la async parola chiave , quando un metodo "void" completa l'esecuzione, deve restituire il valore Task.CompletedTaskspeciale :

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

Metodo di granularità contrassegnato come async restituisce direttamente il valore:

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

Un metodo grain void contrassegnato come async che non restituisce alcun valore termina semplicemente alla fine della sua esecuzione.

public async Task GrainMethod4()
{
    return;
}

Se un metodo grain riceve il valore restituito da un'altra chiamata asincrona del metodo (a un grain o meno) e non deve eseguire la gestione degli errori per tale chiamata, può semplicemente restituire il risultato Task ricevuto da tale chiamata asincrona:

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

    return task;
}

Analogamente, un void metodo grain può restituire un Task restituito da un'altra chiamata anziché attenderlo.

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

ValueTask<T> può essere usato invece di Task<T>.

Riferimenti granulari

Un riferimento granulare è un oggetto proxy che implementa la stessa interfaccia granulare della classe granulare corrispondente. Incapsula l'identità logica (tipo e chiave univoca) del grain di destinazione. Si utilizzano riferimenti ai granuli per effettuare chiamate al granulo di destinazione. Ogni riferimento a un grano punta a un singolo grano (una singola istanza della classe grano), ma è possibile creare più riferimenti indipendenti allo stesso grano.

Poiché un riferimento granulare rappresenta l'identità logica della granularità di destinazione, è indipendente dalla posizione fisica della granularità e rimane valido anche dopo un riavvio completo del sistema. È possibile usare riferimenti granulari come qualsiasi altro oggetto .NET. È possibile passarlo a un metodo, usarlo come valore restituito dal metodo e così via e persino salvarlo nell'archiviazione permanente.

È possibile ottenere un riferimento granulare passando l'identità di una granularità al IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) metodo , dove T è l'interfaccia di granularità ed key è la chiave univoca della granularità all'interno del relativo tipo.

Negli esempi seguenti viene illustrato come ottenere un riferimento granulare per l'interfaccia IPlayerGrain definita in precedenza.

Dall'interno di una classe di granularità:

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

Dal Orleans codice client.

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

Per altre informazioni sui riferimenti granulari, vedere l'articolo di riferimento sulla granularità.

Invocazione del metodo Grain

Il Orleans modello di programmazione si basa sulla programmazione asincrona. Usando il riferimento granulare dell'esempio precedente, ecco come eseguire una chiamata al metodo granulare:

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

È possibile unire due o più Tasks. L'operazione di join crea un nuovo Task oggetto che si risolve quando tutti i suoi componenti Tasks sono completi. Questo modello è utile quando è necessario avviare più calcoli e attendere il completamento di tutti prima di procedere. Ad esempio, un grano front-end che genera una pagina Web composta da molte parti potrebbe effettuare più chiamate back-end (una per ogni parte) e ricevere un Task per ogni risultato. Il grano attenderebbe quindi il join di tutti questi Tasks. Quando il processo di unione Task viene risolto, gli individui Tasks hanno completato e tutti i dati necessari per formattare la pagina web sono stati ricevuti.

Esempio:

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.

Propagazione degli errori

Quando un metodo granulare genera un'eccezione, Orleans propaga tale eccezione allo stack di chiamate, in base alle esigenze. Affinché funzioni come previsto, le eccezioni devono essere serializzabili da Orleanse gli host che gestiscono l'eccezione devono avere il tipo di eccezione disponibile. Se un tipo di eccezione non è disponibile, Orleans genera l'eccezione come istanza di Orleans.Serialization.UnavailableExceptionFallbackException, mantenendo il messaggio, il tipo e l'analisi dello stack dell'eccezione originale.

Le eccezioni generate dai metodi granulari non causano la disattivazione della granularità a meno che l'eccezione non erediti da Orleans.Storage.InconsistentStateException. Le operazioni di archiviazione generano InconsistentStateException quando rilevano che lo stato in-memory del grano non è coerente con lo stato nel database. Oltre alla gestione speciale di InconsistentStateException, questo comportamento è simile al lancio di un'eccezione da qualsiasi oggetto .NET: le eccezioni non causano la distruzione di un oggetto.

Metodi virtuali

Una classe grain può opzionalmente eseguire l'override dei metodi virtuali OnActivateAsync e OnDeactivateAsync. Il Orleans runtime richiama questi metodi all'attivazione e alla disattivazione di ogni istanza della classe. In questo modo, il codice granulare può eseguire operazioni di inizializzazione e pulizia aggiuntive. Un'eccezione generata da OnActivateAsync causa il fallimento del processo di attivazione.

Anche se OnActivateAsync (se sottoposto a override) viene sempre chiamato come parte del processo di attivazione granulare, OnDeactivateAsync non è garantito che venga chiamato in tutte le situazioni (ad esempio, in caso di errore del server o di altri eventi anomali). Per questo motivo, le applicazioni non devono basarsi su OnDeactivateAsync per eseguire operazioni critiche, ad esempio per rendere persistenti le modifiche dello stato. Usarlo solo per le operazioni con il massimo sforzo.

Vedere anche