Partager via


Sérialisation de types immuables dans Orleans

Orleans a une fonctionnalité qui peut être utilisée pour éviter une partie de la surcharge associée à la sérialisation des messages contenant des types immuables. Cette section décrit cette fonctionnalité et son application, en commençant par le contexte où elle est pertinente.

Sérialisation dans Orleans

Quand une méthode de grain est appelée, le runtime Orleans effectue une copie détaillée des arguments de la méthode et forme la demande à partir de ces copies. Cela protège contre la modification des objets d’argument par le code appelant avant que les données soient transmises au grain appelé.

Si le grain appelé se trouve sur un autre silo, les copies sont finalement sérialisées dans un flux d’octets et envoyées via le réseau au silo cible, où elles sont désérialisées à nouveau en objets. Si le grain appelé se trouve sur le même silo, les copies sont transmises directement à la méthode appelée.

Les valeurs de retour sont gérées de la même façon : d’abord copiées, puis éventuellement sérialisées et désérialisées.

Notez que les 3 processus, la copie, la sérialisation et la désérialisation, respectent l’identité des objets. En d’autres termes, si vous transmettez une liste contenant deux fois le même objet, côté réception, vous obtiendrez une liste avec deux fois le même objet, plutôt qu’avec deux objets contenant les mêmes valeurs.

Optimiser la copie

Dans de nombreux cas, la copie approfondie est inutile. Par exemple, un scénario possible présente un front-end web qui reçoit un tableau d’octets de son client et transmet cette demande, y compris le tableau d’octets, à un grain en vue de son traitement. Le processus front-end ne fait rien avec le tableau une fois qu’il l’a transmis au grain. Notamment, il ne réutilise pas le tableau pour recevoir une demande ultérieure. Dans le grain, le tableau d’octets est analysé en vue de la récupération des données d’entrée, mais il n’est pas modifié. Le grain retourne un autre tableau d’octets qu’il a créé pour être transmis en retour au client web. Il supprime ce tableau dès qu’il l’a retourné. Le front-end web transmet le tableau d’octets résultant à son client, sans modification.

Dans ce scénario, il n’est pas nécessaire de copier les tableaux d’octets de demande et de réponse. Malheureusement, le runtime Orleans ne peut pas le savoir, car il ne peut pas déterminer si les tableaux sont modifiés ou non ultérieurement par le front-end web ou par le grain. Dans un monde idéal, nous disposerions d’une sorte de mécanisme .NET pour indiquer qu’une valeur n’est plus modifiée. À défaut de cela, nous avons ajouté des mécanismes spécifiques à Orleans à cet effet : la classe wrapper Immutable<T> et ImmutableAttribute.

Utiliser l’attribut [Immutable] pour rendre un type, un paramètre, une propriété ou un champ immuable

Pour les types définis par l’utilisateur, ImmutableAttribute peut être ajouté au type. Cela indique au sérialiseur d’Orleans d’éviter de copier des instances de ce type. L’extrait de code suivant illustre l’utilisation de [Immutable] pour désigner un type immuable. Ce type ne sera pas copié pendant la transmission.

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

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

Dans certaines circonstances, vous n’avez peut-être pas le contrôle de l’objet. Par exemple, il peut s’agir d’un List<int> que vous envoyez entre les grains. Dans d’autres circonstances, certaines parties de vos objets sont peut-être immuables, contrairement à d’autres parties. Dans les cas présents, Orleans prend en charge des options supplémentaires.

  1. Les signatures de méthodes peuvent inclure ImmutableAttribute pour chaque paramètre :

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Les propriétés et les champs individuels peuvent être marqués comme étant ImmutableAttribute pour éviter la création de copies au moment où des instances du type conteneur sont copiées.

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

Utilisez Immutable<T>.

La classe wrapper Immutable<T> permet d’indiquer qu’une valeur peut être considérée comme immuable. Autrement dit, la valeur sous-jacente n’est pas modifiée, de sorte qu’aucune copie n’est requise pour le partage sécurisé. Notez que l’utilisation de Immutable<T> implique que ni le fournisseur de la valeur ni son destinataire ne la modifieront à l’avenir. Il ne s’agit pas d’un engagement unilatéral, mais plutôt d’un engagement bilatéral mutuel.

Pour utiliser Immutable<T> dans votre interface de grain, au lieu de transmettre T, transmettez Immutable<T>. Par exemple, dans le scénario décrit ci-dessus, la méthode de grain était :

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

Elle devient alors :

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

Pour créer un élément Immutable<T>, utilisez simplement le constructeur :

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

Pour obtenir les valeurs à l’intérieur de l’élément immuable, utilisez la propriété .Value :

byte[] buffer = immutable.Value;

Immuabilité dans Orleans

Pour Orleans, l’immuabilité est une instruction plutôt stricte : le contenu de l’élément de données ne sera modifié en aucune manière susceptible de changer la signification sémantique de l’élément, ou qui interférerait avec un autre thread accédant simultanément à l’élément. Le moyen le plus sûr de garantir cela consiste simplement à ne pas modifier du tout l’élément : immuabilité au niveau du bit, plutôt qu’immuabilité logique.

Dans certains cas, il n’y a pas de risque à assouplir cela en une immuabilité logique, mais il faut veiller à ce que le code qui mute soit bien thread-safe. Comme la gestion du multithreading est complexe et rare dans un contexte Orleans, nous vous déconseillons vivement cette approche et vous recommandons de vous en tenir à l’immuabilité au niveau du bit.