Freigeben über


Serialisierung unveränderlicher Typen in Orleans

Orleans verfügt über ein Feature, das verwendet werden kann, um einen Teil des Mehraufwands zu vermeiden, der mit der Serialisierung von Meldungen einhergeht, die unveränderliche Typen enthalten. In diesem Abschnitt werden das Feature und seine Anwendung beschrieben, beginnend mit dem Kontext, in dem es relevant ist.

Serialisierung in Orleans

Wenn eine Grainmethode aufgerufen wird, erstellt die Orleans-Runtime eine Tiefenkopie der Methodenargumente und bildet die Anforderung aus den Kopien. Dies schützt davor, dass der aufrufende Code die Argumentobjekte ändert, bevor die Daten an das aufgerufene Grain übergeben werden.

Wenn sich das aufgerufene Grain in einem anderen Silo befindet, werden die Kopien schließlich in einen Bytestream serialisiert und über das Netzwerk an das Zielsilo gesendet, wo sie wieder in Objekte deserialisiert werden. Befindet sich das aufgerufene Grain im selben Silo, werden die Kopien direkt an die aufgerufene Methode übergeben.

Rückgabewerte werden auf die gleiche Weise behandelt: zuerst kopiert, anschließend möglicherweise serialisiert und dann wieder deserialisiert.

Beachten Sie, dass alle drei Prozesse (Kopieren, Serialisieren und Deserialisieren) die Objektidentität respektieren. Anders ausgedrückt: Wenn Sie eine Liste übergeben, die dasselbe Objekt zweimal enthält, erhalten Sie auf der empfangenden Seite eine Liste, die dasselbe Objekt zweimal enthält, anstatt zwei Objekte mit denselben Werten darin.

Optimieren des Kopierens

In vielen Fällen sind keine Tiefenkopien erforderlich. Ein mögliches Szenario wäre z. B. ein Web-Front-End, das ein Bytearray vom Client empfängt und diese Anforderung einschließlich des Bytearrays zur Verarbeitung an ein Grain übergibt. Der Front-End-Prozess führt keine Aktionen mit dem Array aus, nachdem es an das Grain übergeben wurde. Insbesondere wird das Array nicht für zukünftige Anforderungen wiederverwendet. Innerhalb des Grains wird das Bytearray geparst, um die Eingabedaten abzurufen, aber es wird nicht geändert. Das Grain gibt ein weiteres Bytearray zurück, das erstellt wurde, um an den Webclient zurückgegeben zu werden. Das Array wird direkt nach der Rückgabe verworfen. Das Web-Front-End übergibt das Ergebnisbytearray ohne Änderung zurück an den Client.

In einem solchen Szenario ist es nicht erforderlich, die Bytearrys in der Anforderung oder der Antwort zu kopieren. Leider kann die Orleans-Runtime dies nicht selbst herausfinden, da sie nicht erkennen kann, ob die Arrays später vom Web-Front-End oder vom Grain geändert werden. Im Idealfall hätten Sie eine Art .NET-Mechanismus, der angibt, dass ein Wert nicht mehr geändert wird. Da es solchen Mechanismus nicht gibt, wurden dafür Orleans-spezifische Mechanismen hinzugefügt: die Immutable<T>-Wrapperklasse und das ImmutableAttribute.

Sie verwenden das [Immutable]-Attribut, um einen Typ, einen Parameter, eine Eigenschaft oder ein Feld als unveränderlich zu kennzeichnen.

Bei benutzerdefinierten Typen kann dem Typ das ImmutableAttribute hinzugefügt werden. Dadurch wird das Serialisierungsprogramm von Orleans angewiesen, das Kopieren von Instanzen dieses Typs zu vermeiden. Der folgende Codeschnipsel veranschaulicht die Verwendung von [Immutable], um einen Typ als unveränderlich zu kennzeichnen. Dieser Typ wird während der Übertragung nicht kopiert.

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

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

Mitunter haben Sie keine Kontrolle über das Objekt, z. B. wenn es sich um ein List<int>-Element handelt, das Sie zwischen Grains senden. In anderen Fällen sind vielleicht Teile Ihrer Objekte unveränderlich und andere Teile nicht. Für diese Fälle unterstützt Orleans zusätzliche Optionen.

  1. Methodensignaturen können auf Parameterbasis ImmutableAttribute enthalten:

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Einzelne Eigenschaften und Felder können als ImmutableAttribute markiert werden, um zu verhindern, dass Kopien erstellt werden, wenn Instanzen des enthaltenden Typs kopiert werden.

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

Verwenden Sie Immutable<T>

Mit der Immutable<T>-Wrapperklasse wird angegeben, dass ein Wert als unveränderlich angesehen werden kann. Das heißt, der zugrunde liegende Wert wird nicht geändert, sodass für die sichere Weitergabe kein Kopieren erforderlich ist. Beachten Sie, dass die Verwendung von Immutable<T> impliziert, dass der Wert weder beim Übergen noch beim Empfang in Zukunft geändert wird. Es handelt sich nicht um eine einseitige Verpflichtung, sondern um eine gegenseitige Verpflichtung.

Um in Ihrer Grainschnittstelle Immutable<T> zu verwenden, übergeben Sie Immutable<T> anstelle von T. Im oben beschriebenen Szenario lautete die Grainmethode wie folgt:

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

Dies würde wie folgt geändert werden:

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

Verwenden Sie einfach den Konstruktor, um eine Immutable<T> zu erstellen:

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

Verwenden Sie die .Value-Eigenschaft, um die Werte innerhalb des unveränderlichen Typs abzurufen:

byte[] buffer = immutable.Value;

Unveränderlichkeit in Orleans

Für die Zwecke in Orleans ist Unveränderlichkeit eine relativ strenge Anweisung: Der Inhalt des Datenelements wird niemals geändert, wenn dies die semantische Bedeutung des Elements ändern könnte oder einen anderen Thread beeinträchtigen würde, der gleichzeitig auf das Element zugreift. Die sicherste Möglichkeit, dies sicherzustellen, besteht darin, das Element einfach nicht zu ändern: Es handelt sich also um bitweise Unveränderlichkeit statt logische Unveränderlichkeit.

In einigen Fällen ist es sicher, lediglich logische Unveränderlichkeit zu fordern, aber es muss darauf geachtet werden, dass Code, der Änderungen vornimmt, wirklich threadsicher ist. Da der Umgang mit Multithreading komplex und im Kontext von Orleans ungewöhnlich ist, wird von diesem Ansatz dringend abgeraten und stattdessen empfohlen, bei der bitweisen Unveränderlichkeit zu bleiben.