Dela via


Serialisering av oföränderliga typer i Orleans

Orleans har en funktion som kan användas för att undvika vissa av de omkostnader som är associerade med serialiseringsmeddelanden som innehåller oföränderliga typer. I det här avsnittet beskrivs funktionen och dess program, med början i kontexten om var den är relevant.

Serialisering i Orleans

När en kornmetod anropas gör körningen Orleans en djup kopia av metodargumenten och bildar begäran från kopiorna. Detta skyddar mot den anropande koden som ändrar argumentobjekten innan data skickas till det anropade kornet.

Om det anropade kornet finns på en annan silo serialiseras kopiorna så småningom till en byteström och skickas via nätverket till målsilon, där de deserialiseras tillbaka till objekt. Om det anropade kornet finns på samma silo, överlämnas kopiorna direkt till den anropade metoden.

Returvärden hanteras på samma sätt: kopieras först och kan sedan serialiseras och deserialiseras.

Observera att alla tre processer, kopiering, serialisering och deserialisering respekterar objektidentitet. Med andra ord, om du skickar en lista som har samma objekt i den två gånger, får du på mottagarsidan en lista med samma objekt i den två gånger, i stället för med två objekt med samma värden i dem.

Optimera kopiering

I många fall är djupkopiering onödigt. Ett möjligt scenario är till exempel en webbklientdel som tar emot en bytematris från klienten och skickar begäran, inklusive bytematrisen, till ett korn för bearbetning. Klientdelsprocessen gör ingenting med matrisen när den har överfört den till kornigheten. I synnerhet återanvänds inte matrisen för att ta emot en framtida begäran. I kornet parsas bytematrisen för att hämta indata, men ändras inte. Kornet returnerar en annan bytematris som den har skapat för att skickas tillbaka till webbklienten. den tar bort matrisen så snart den returnerar den. Webbklientdelen skickar tillbaka resultatbytematrisen till klienten utan ändringar.

I ett sådant scenario behöver du inte kopiera antingen bytematriserna för begäran eller svar. Tyvärr kan körningen Orleans inte räkna ut detta på egen hand, eftersom det inte går att avgöra om matriserna ändras senare av webbklientdelen eller av kornigheten. I bästa möjliga världar skulle vi ha någon form av .NET-mekanism för att indikera att ett värde inte längre ändras. Utan det har vi lagt till Orleans- specifika mekanismer för detta: omslutningsklassen Immutable<T> ImmutableAttributeoch .

[Immutable] Använd attributet för att göra en typ, parameter, egenskap eller fält som oföränderlig

För användardefinierade typer ImmutableAttribute kan du lägga till den i typen. Detta instruerar Orleansserialiseraren att undvika att kopiera instanser av den här typen. Följande kodfragment visar hur du använder [Immutable] för att ange en oföränderlig typ. Den här typen kopieras inte under överföringen.

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

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

Ibland kanske du inte har kontroll över objektet, till exempel kan det vara en List<int> som du skickar mellan korn. Andra gånger kanske delar av dina objekt är oföränderliga och andra delar inte är det. I dessa fall Orleans har stöd för ytterligare alternativ.

  1. Metodsignaturer kan innehålla ImmutableAttribute per parameter:

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Enskilda egenskaper och fält kan markeras för ImmutableAttribute att förhindra att kopior görs när instanser av den innehållande typen kopieras.

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

Använda Immutable<T>

Omslutningsklassen Immutable<T> används för att indikera att ett värde kan betraktas som oföränderligt, det vill s.v.s. att det underliggande värdet inte ändras, så ingen kopiering krävs för säker delning. Observera att användning Immutable<T> innebär att varken leverantören av värdet eller mottagaren av värdet kommer att ändra det i framtiden. Det är inte ett ensidigt åtagande, utan snarare ett ömsesidigt åtagande med dubbla sidor.

Om du vill använda Immutable<T> i korngränssnittet i stället för att skicka Tskickar du Immutable<T>. I det ovan beskrivna scenariot var metoden grain till exempel:

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

Vilket då skulle bli:

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

Om du vill skapa en Immutable<T>använder du helt enkelt konstruktorn:

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

Om du vill hämta värdena i det oföränderliga använder du egenskapen .Value :

byte[] buffer = immutable.Value;

Oföränderlighet i Orleans

För Orleans" ändamål är oföränderlighet en ganska strikt instruktion: innehållet i dataobjektet kommer inte att ändras på något sätt som kan ändra objektets semantiska betydelse, eller som skulle störa en annan tråd som samtidigt kommer åt objektet. Det säkraste sättet att säkerställa detta är att helt enkelt inte ändra objektet alls: bitvis oföränderlighet, snarare än logisk oföränderlighet.

I vissa fall är det säkert att koppla detta till logisk oföränderlighet, men du måste vara noga med att se till att muteringskoden är korrekt trådsäker. Eftersom det är komplext och ovanligt att hantera multitrådning i ett Orleans sammanhang rekommenderar vi starkt mot den här metoden och rekommenderar att du håller dig till bitvis oföränderlighet.