Condividi tramite


Serializzazione di tipi non modificabili in Orleans

Orleans dispone di una funzionalità che può essere usata per evitare un sovraccarico associato alla serializzazione dei messaggi contenenti tipi non modificabili. Questa sezione descrive la funzionalità e la relativa applicazione, a partire dal contesto in cui è rilevante.

Serializzazione in Orleans

Quando viene richiamato un metodo granulare, il runtime Orleans esegue una copia completa degli argomenti del metodo e forma la richiesta dalle copie. Ciò protegge dal codice chiamante che modifica gli oggetti argomento prima che i dati vengano passati al grano chiamato.

Se il grano chiamato si trova su un silo diverso, le copie vengono infine serializzate in un flusso di byte e inviate in rete al silo di destinazione, dove vengono deserializzate di nuovo negli oggetti. Se il grano chiamato si trova nello stesso silo, le copie vengono passate direttamente al metodo chiamato.

I valori restituiti vengono gestiti nello stesso modo: prima copiati, quindi eventualmente serializzati e deserializzati.

Si noti che tutti e 3 i processi, la copia, la serializzazione e la deserializzazione, rispettano l'identità dell'oggetto. In altre parole, se si passa un elenco che contiene due volte lo stesso oggetto, dal lato ricevente si otterrà un elenco con lo stesso oggetto due volte, anziché due oggetti con gli stessi valori.

Ottimizzare la copia

In molti casi, la copia approfondita non è necessaria. Ad esempio, un possibile scenario è un front-end Web che riceve una matrice di byte dal client e passa tale richiesta, inclusa la matrice di byte, su una granularità per l'elaborazione. Il processo front-end non esegue alcuna operazione con la matrice dopo averlo passato alla granularità; in particolare, non riutilizza la matrice per ricevere una richiesta futura. All'interno della granularità, la matrice di byte viene analizzata per recuperare i dati di input, ma non viene modificata. La granularità restituisce un'altra matrice di byte creata per tornare al client Web; rimuove la matrice non appena viene restituita. Il front-end Web passa nuovamente la matrice di byte dei risultati al client, senza alcuna modifica.

In uno scenario di questo tipo non è necessario copiare le matrici di byte di richiesta o risposta. Sfortunatamente, il runtime di Orleans non riesce a capirlo da solo, poiché non è in grado di indicare se le matrici vengono modificate in un secondo momento dal front-end Web o dal livello di granularità. Nel migliore dei mondi possibili, avremmo una sorta di meccanismo .NET per indicare che un valore non è più modificato; in mancanza di questo, sono stati aggiunti dei meccanismi specifici di Orleans per questo: la classe wrapper Immutable<T> e ImmutableAttribute.

Usare l'attributo [Immutable] per rendere non modificabile un tipo, un parametro, una proprietà o un campo

Per i tipi definiti dall'utente, è possibile aggiungere ImmutableAttribute al tipo. Indica al serializzatore di Orleans di evitare di copiare istanze di questo tipo. Il frammento di codice seguente illustra l'uso di [Immutable] per indicare un tipo non modificabile. Questo tipo non verrà copiato durante la trasmissione.

[Immutable]
public class MyImmutableType
{
    public int MyValue { get; }

    public MyImmutableType(int value)
    {
        MyValue = value;
    }
}

A volte, è possibile che non si abbia il controllo sull'oggetto, ad esempio potrebbe trattarsi di un oggetto List<int> che si invia tra grani. Altre volte, forse alcune parti degli oggetti sono immutabili e altre parti non lo sono. Per questi casi, Orleans supporta opzioni aggiuntive.

  1. Le firme dei metodi possono includere ImmutableAttribute in base ai parametri:

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Le singole proprietà e i campi possono essere contrassegnati come ImmutableAttribute per impedire l'esecuzione di copie quando vengono copiate istanze del tipo contenitore.

    [GenerateSerializer]
    public sealed class MyType
    {
        [Id(0), Immutable]
        public List<int> ReferenceData { get; set; }
    
        [Id(1)]
        public List<int> RunningTotals { get; set; }
    }
    

Utilizzare Immutable<T>.

La classe wrapper Immutable<T> viene usata per indicare che un valore può essere considerato non modificabile; ovvero, il valore sottostante non verrà modificato, quindi non è necessaria alcuna copia per la condivisione sicura. Si noti che l'utilizzo di Immutable<T> implica che né il provider del valore né il destinatario del valore lo modificheranno in futuro; non è un impegno unilaterale, ma piuttosto un impegno reciproco a doppio senso.

Per usare Immutable<T> nell'interfaccia granulare, anziché passare T, passare Immutable<T>. Ad esempio, nello scenario descritto in precedenza, il metodo granulare era:

Task<byte[]> ProcessRequest(byte[] request);

Che sarebbe poi diventato:

Task<Immutable<byte[]>> ProcessRequest(Immutable<byte[]> request);

Per creareImmutable<T>, usare semplicemente il costruttore :

Immutable<byte[]> immutable = new(buffer);

Per ottenere i valori all'interno dell'oggetto non modificabile, usare la proprietà .Value:

byte[] buffer = immutable.Value;

Immutabilità in Orleans

Ai fini di Orleans, l'immutabilità è un'istruzione piuttosto rigorosa: il contenuto dell'elemento di dati non verrà modificato in alcun modo che possa modificare il significato semantico dell'elemento o che interferisca con un altro thread contemporaneamente all'accesso all'elemento. Il modo più sicuro per garantirlo è semplicemente quello di non modificare affatto l'elemento: immutabilità bit per bit, anziché l'immutabilità logica.

In alcuni casi, è consigliabile rilassarsi con l'immutabilità logica, ma è necessario prestare attenzione per garantire che il codice di modifica sia correttamente thread-safe. Poiché la gestione del multithreading è complessa e non comune in un contesto di Orleans, sconsigliamo vivamente questo approccio e raccomandiamo di attenersi all'immutabilità bir per bit.