Udostępnij za pośrednictwem


Serializacja niezmiennych typów w Orleans

Orleans ma funkcję, która może służyć do uniknięcia niektórych obciążeń związanych z serializacją komunikatów zawierających niezmienne typy. W tej sekcji opisano funkcję i jej aplikację, zaczynając od kontekstu, w którym jest ona odpowiednia.

Serializacja w programie Orleans

Po wywołaniu Orleans metody ziarna środowisko uruchomieniowe wykonuje głęboką kopię argumentów metody i tworzy żądanie z kopii. Chroni to przed wywoływanym kodem modyfikującym obiekty 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 skopiowane, a następnie prawdopodobnie serializowane i deserializowane.

Należy pamiętać, że wszystkie 3 procesy, kopiowanie, serializowanie i deserializacja, szanują tożsamość obiektu. Innymi słowy, jeśli przekażesz listę, która ma w nim ten sam obiekt dwa razy, po stronie odbieranej otrzymasz listę z tym samym obiektem dwukrotnie, a nie z dwoma obiektami o tych samych wartościach.

Optymalizowanie kopiowania

W wielu przypadkach kopiowanie głębokie jest niepotrzebne. Na przykład możliwym scenariuszem jest fronton internetowy, który odbiera tablicę bajtów od klienta i przekazuje to żądanie, w tym tablicę bajtów, na ziarno do przetwarzania. Proces frontonu nie wykonuje żadnych czynności z tablicą po przekazaniu jej do ziarna; w szczególności tablica nie jest ponownie wykorzystywana do odbierania przyszłego żądania. Wewnątrz ziarna tablica bajtów jest analizowana w celu pobrania danych wejściowych, ale nie jest modyfikowana. Ziarno zwraca kolejną tablicę bajtów utworzoną w celu przekazania z powrotem do klienta internetowego; odrzuca tablicę, gdy tylko ją zwróci. 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 uruchomieniowe nie może ustalić tego samodzielnie, ponieważ nie może określić, Orleans czy tablice są modyfikowane później przez fronton internetowy, czy przez ziarno. W najlepszym przypadku wszystkich możliwych światów mielibyśmy jakiś mechanizm platformy .NET wskazujący, że wartość nie jest już modyfikowana; w tym celu dodaliśmy Orleans-specyficzne mechanizmy: klasę Immutable<T> otoki i klasę ImmutableAttribute.

Użyj atrybutu [Immutable] , aby ustawić 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 Orleansto, ż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 może nie mieć kontroli nad obiektem, na przykład może być to List<int> , że wysyłasz między ziarnami. Innym razem, być może części obiektów są niezmienne, a inne części nie są. W takich przypadkach Orleans obsługuje dodatkowe opcje.

  1. Podpisy metod mogą być uwzględniane ImmutableAttribute dla poszczególnych parametrów:

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Poszczególne właściwości i pola można oznaczyć jako ImmutableAttribute , aby zapobiec kopiowaniu kopii 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; }
    }
    

Korzystanie z polecenia Immutable<T>

Klasa Immutable<T> otoki służy do wskazywania, że wartość może być uznawana za niezmienną. Oznacza to, że wartość bazowa 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 wartości, ani odbiorca wartości nie zmodyfikują go w przyszłości; nie jest to jednostronne zobowiązanie, ale raczej wzajemne zobowiązanie dwustronne.

Aby użyć Immutable<T> w interfejsie ziarna, zamiast przekazywać T, przekaż Immutable<T>polecenie . Na przykład w powyższym opisanym scenariuszu metoda ziarna:

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 konstruktora:

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

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

byte[] buffer = immutable.Value;

Niezmienność w Orleans

W Orleanscelach " niezmienność jest dość ścisłą instrukcją: zawartość elementu danych nie zostanie zmodyfikowana w żaden sposób, co może zmienić znaczenie semantyczne elementu lub zakłócałoby to jednoczesne uzyskiwanie dostępu do elementu przez inny wątek. Najbezpieczniejszym sposobem zapewnienia, że jest to po prostu nie modyfikowanie elementu w ogóle: niezmienność bitowa, a nie logiczna niezmienność.

W niektórych przypadkach bezpieczne jest złagodzenie tego problemu z logiczną 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ątków jest złożona i nietypowa w Orleans kontekście, zdecydowanie zalecamy stosowanie tego podejścia i zaleca się trzymanie się niezmienności bitowej.