Condividi tramite


Osservatori

Esistono situazioni in cui un semplice modello di messaggio/risposta non è sufficiente e il client deve ricevere notifiche asincrone. Ad esempio, un utente potrebbe voler ricevere una notifica quando un nuovo messaggio istantaneo è stato pubblicato da un amico.

Gli osservatori client sono un meccanismo che consente di inviare notifiche ai client in modo asincrono. Le interfacce Observer devono ereditare da IGrainObserver, e tutti i metodi devono restituire void, Task, Task<TResult> ValueTask, o ValueTask<TResult>. Un tipo restituito di void non è consigliato perché può incoraggiare l'uso di async void nell'implementazione, che è un modello pericoloso perché può causare arresti anomali dell'applicazione se viene generata un'eccezione dal metodo. Per gli scenari di notifica più impegnativi, è consigliabile applicare OneWayAttribute al metodo di interfaccia dell'osservatore. In questo modo il ricevitore non invierà una risposta per la chiamata al metodo e causerà la restituzione immediata del metodo nel sito di chiamata, senza attendere una risposta dall'osservatore. Una granularità chiama un metodo su un osservatore richiamandolo come qualsiasi metodo di interfaccia granulare. Il runtime Orleans garantisce il recapito di richieste e risposte. Un caso d'uso comune per gli osservatori consiste nell'integrare un client per ricevere delle notifiche quando si verifica un evento nell'applicazione Orleans. Una granularità che pubblica tali notifiche deve fornire un'API per aggiungere o rimuovere osservatori. Inoltre, è in genere utile esporre un metodo che consente l'annullamento di una sottoscrizione esistente.

Gli sviluppatori di granularità possono usare una classe di utilità, ad esempio ObserverManager<TObserver> per semplificare lo sviluppo di tipi di granularità osservati. A differenza dei grani, che vengono riattivati automaticamente in base alle esigenze dopo l'errore, i client non sono a tolleranza di errore: un client non riuscito può non recuperare mai. Per questo motivo, l'utilità ObserverManager<T> rimuove le sottoscrizioni dopo una durata configurata. I client attivi devono ripetere la sottoscrizione in un timer per mantenere attiva la sottoscrizione.

Per sottoscrivere una notifica, il client deve prima creare un oggetto locale che implementa l'interfaccia dell’osservatore. Chiama quindi un metodo sulla factory dell’osservatore, CreateObjectReference', per trasformare l'oggetto in un riferimento granulare, che può quindi essere passato al metodo di sottoscrizione sulla granularità di notifica.

Questo modello può essere usato anche da altri grani per ricevere notifiche asincrone. I grani possono anche implementare interfacce IGrainObserver. A differenza del caso della sottoscrizione client, il livello di sottoscrizione implementa semplicemente l'interfaccia dell’osservatore e passa un riferimento a se stesso (ad esempio this.AsReference<IMyGrainObserverInterface>()). Non c'è bisogno di CreateObjectReference() perché i grani sono già indirizzabili.

Esempio di codice

Si supponga di avere una granularità che invia periodicamente messaggi ai client. Per semplicità, il messaggio nell'esempio sarà una stringa. Per prima cosa si definisce l'interfaccia nel client che riceverà il messaggio.

L'interfaccia avrà un aspetto simile al seguente

public interface IChat : IGrainObserver
{
    Task ReceiveMessage(string message);
}

L'unica cosa speciale è che l'interfaccia deve ereditare da IGrainObserver. Ora qualsiasi client che vuole osservare tali messaggi deve implementare una classe che implementa IChat.

Il caso più semplice è simile al seguente:

public class Chat : IChat
{
    public Task ReceiveMessage(string message)
    {
        Console.WriteLine(message);
        return Task.CompletedTask;
    }
}

Nel server dovrebbe essere disponibile un oggetto Grano che invia questi messaggi di chat ai client. La granularità deve inoltre avere un meccanismo per consentire ai client di sottoscrivere e annullare la sottoscrizione per le notifiche. Per le sottoscrizioni, la granularità può usare un'istanza della classe di utilità ObserverManager<TObserver>.

Nota

ObserverManager<TObserver> fa parte di Orleans dalla versione 7.0. Per le versioni precedenti, è possibile copiare l’implementazione seguente.

class HelloGrain : Grain, IHello
{
    private readonly ObserverManager<IChat> _subsManager;

    public HelloGrain(ILogger<HelloGrain> logger)
    {
        _subsManager =
            new ObserverManager<IChat>(
                TimeSpan.FromMinutes(5), logger);
    }

    // Clients call this to subscribe.
    public Task Subscribe(IChat observer)
    {
        _subsManager.Subscribe(observer, observer);

        return Task.CompletedTask;
    }

    //Clients use this to unsubscribe and no longer receive messages.
    public Task UnSubscribe(IChat observer)
    {
        _subsManager.Unsubscribe(observer);

        return Task.CompletedTask;
    }
}

Per inviare un messaggio ai client, è possibile usare il metodo Notify dell'istanza di ObserverManager<IChat>. Il metodo accetta un metodo Action<T> o un'espressione lambda (dove T è di tipo IChat). È possibile chiamare qualsiasi metodo sull'interfaccia per inviarlo ai client. In questo caso è presente un solo metodo, ReceiveMessage, e il codice di invio nel server sarà simile al seguente:

public Task SendUpdateMessage(string message)
{
    _subsManager.Notify(s => s.ReceiveMessage(message));

    return Task.CompletedTask;
}

Il server dispone ora di un metodo per inviare messaggi ai client osservatori, due metodi per la sottoscrizione/annullamento della sottoscrizione, e il client ha implementato una classe in grado di osservare i messaggi di granularità. L'ultimo passaggio consiste nel creare un riferimento osservatore sul client usando la classe Chat implementata in precedenza e consentire la ricezione dei messaggi dopo la sottoscrizione.

Il codice sarà simile al seguente:

//First create the grain reference
var friend = _grainFactory.GetGrain<IHello>(0);
Chat c = new Chat();

//Create a reference for chat, usable for subscribing to the observable grain.
var obj = _grainFactory.CreateObjectReference<IChat>(c);

//Subscribe the instance to receive messages.
await friend.Subscribe(obj);

A questo punto, ogni volta che il server chiama il metodo SendUpdateMessage, tutti i client sottoscritti riceveranno il messaggio. Nel codice client, l'istanza Chat nella variabile c riceverà il messaggio e lo restituirà nella console.

Importante

Gli oggetti passati a CreateObjectReference vengono mantenuti tramite WeakReference<T> e verranno quindi sottoposti a Garbage Collection se non esistono altri riferimenti.

Gli utenti devono mantenere un riferimento per ogni osservatore che non desiderano raccogliere.

Nota

Gli osservatori sono intrinsecamente inaffidabili perché un client che ospita un osservatore può non riuscire e gli osservatori creati dopo il ripristino hanno identità diverse (casuali). ObserverManager<TObserver> si basa sulla riscrizione periodica da parte degli osservatori, come illustrato in precedenza, in modo che gli osservatori inattivi possano essere rimossi.

Modello di esecuzione

Le implementazioni di IGrainObserver vengono registrate tramite una chiamata a IGrainFactory.CreateObjectReference e ogni chiamata a tale metodo crea un nuovo riferimento che punta a tale implementazione. Orleans eseguirà le richieste inviate a ognuno di questi riferimenti uno alla volta, fino al completamento. Gli osservatori non sono rientranti e pertanto le richieste simultanee a un osservatore non verranno intercalate da Orleans. Se sono presenti più osservatori che ricevono richieste contemporaneamente, tali richieste possono essere eseguite in parallelo. L'esecuzione di metodi osservatore non è interessata da attributi come AlwaysInterleaveAttribute o ReentrantAttribute: il modello di esecuzione non può essere personalizzato da uno sviluppatore.