Udostępnij przez


Serializacja niezmiennych typów w Orleans

Orleans Ma funkcję, której można użyć, aby uniknąć pewnych obciążeń związanych z serializacją komunikatów zawierających niezmienialne typy. W tej sekcji opisano funkcję i jej aplikację, zaczynając od kontekstu, w którym jest ona odpowiednia.

Serializacja w Orleans

Podczas wywoływania metody ziarna, runtime wykonuje głęboką kopię parametrów metody i tworzy żądanie na podstawie tych kopii. Chroni to przed tym, aby kod wywołujący nie modyfikował obiektów argumentów przed przekazaniem danych do wywoływanego ziarna.

Jeśli wywoływane ziarno znajduje się na innym silosie, kopie są ostatecznie serializowane do strumienia bajtów i wysyłane przez sieć do silosu docelowego, gdzie są deserializowane z powrotem do obiektów. Jeśli wywoływane ziarno znajduje się na tym samym silosie, kopie są przekazywane bezpośrednio do wywoływanej metody.

Wartości zwracane są obsługiwane w taki sam sposób: najpierw są kopiowane, a następnie mogą być serializowane i deserializowane.

Należy pamiętać, że wszystkie trzy procesy — kopiowanie, serializowanie i deserializacji — szanują tożsamość obiektu. Innymi słowy, jeśli przekażesz listę zawierającą ten sam obiekt dwa razy, po stronie odbierającej zostanie dwukrotnie wyświetlona lista z tym samym obiektem, a nie dwoma obiektami o tych samych wartościach.

Optymalizowanie kopiowania

W wielu przypadkach kopiowanie głębokie jest niepotrzebne. Rozważmy na przykład scenariusz, w którym interfejs użytkownika otrzymuje tablicę bajtów od klienta i przekazuje to żądanie, wraz z tablicą bajtów, do komponentu przetwarzającego. Proces interfejsu użytkownika nie wykonuje żadnych czynności z tablicą po jej przekazaniu do ziarna; w szczególności nie używa ponownie tablicy dla przyszłych żądań. Wewnątrz ziarna tablica bajtów jest analizowana w celu pobrania danych wejściowych, ale nie jest modyfikowana. Ziarno zwraca klientowi sieciowemu kolejną stworzoną tablicę bajtów i usuwa ją natychmiast po jej zwróceniu. Fronton internetowy przekazuje tablicę bajtów wyników z powrotem do klienta bez modyfikacji.

W takim scenariuszu nie ma potrzeby kopiowania tablic bajtów żądania lub odpowiedzi. Niestety środowisko Orleans uruchomieniowe nie może ustalić tego automatycznie, ponieważ nie może określić, czy interfejs webowy lub ziarno modyfikuje tablice później. W idealnym przypadku mechanizm .NET wskazuje, że wartość nie jest już modyfikowana. W tym celu dodaliśmy mechanizmy specyficzne dla Orleans: klasę Immutable<T> otoki i klasę ImmutableAttribute.

Użyj atrybutu [Immutable] , aby oznaczyć typ, parametr, właściwość lub pole jako niezmienne

W przypadku typów zdefiniowanych przez użytkownika można dodać element ImmutableAttribute do typu . Powoduje to, Orleans że serializator unika kopiowania wystąpień tego typu. Poniższy fragment kodu demonstruje użycie metody [Immutable] , aby oznaczyć niezmienny typ. Ten typ nie zostanie skopiowany podczas transmisji.

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

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

Czasami nie można kontrolować obiektu; na przykład może to być List<int>, który wysyłasz między ziarna. Innym razem części obiektów mogą być niezmienne, podczas gdy inne nie są. W takich przypadkach Orleans obsługuje dodatkowe opcje.

  1. Sygnatury metod mogą zawierać ImmutableAttribute dla poszczególnych parametrów:

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Oznacz poszczególne właściwości i pola jako ImmutableAttribute, aby zapobiec kopiowaniu ich podczas kopiowania wystąpień typu zawierającego.

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

Użyj Immutable<T>

Immutable<T> Użyj klasy otoki, aby wskazać, że wartość może być uznawana za niezmienną. Oznacza to, że podstawowa wartość nie zostanie zmodyfikowana, więc żadne kopiowanie nie jest wymagane do bezpiecznego udostępniania. Należy pamiętać, że użycie Immutable<T> oznacza, że ani dostawca, ani odbiorca wartości nie zmodyfikują jej w przyszłości. Jest to wzajemne, dwustronne zobowiązanie, a nie jednostronne.

Aby użyć Immutable<T> w interfejsu ziarna, przekaż Immutable<T> zamiast T. Na przykład w opisanym powyżej scenariuszu zastosowano metodę ziarnistą:

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

Co wtedy stanie się:

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

Aby utworzyć obiekt Immutable<T>, po prostu użyj jego konstruktora:

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

Aby uzyskać wartość wewnątrz niezmiennego opakowania, użyj właściwości .Value.

byte[] buffer = immutable.Value;

Niezmienność w Orleans

Dla celów Orleans, niezmienność jest ścisłym twierdzeniem: zawartość elementu danych nie zostanie zmodyfikowana w żaden sposób, który mógłby zmienić semantyczne znaczenie elementu lub zakłócać dostęp do niego przez inny wątek jednocześnie. Najbezpieczniejszym sposobem, aby to osiągnąć, jest całkowite niestosowanie modyfikacji elementu: użyj niezmienności bitowej zamiast niezmienności logicznej.

W niektórych przypadkach bezpieczne jest złagodzenie tego problemu w celu logicznej niezmienności, ale należy zadbać o to, aby upewnić się, że kod mutujący jest prawidłowo bezpieczny wątkowo. Ponieważ obsługa wielowątkowości jest złożona i nietypowa w Orleans kontekście, zdecydowanie odradzamy to podejście i doradzamy trzymanie się niezmienności bitowej.