Поделиться через


Сериализация неизменяемых типов в Orleans

Orleans имеет функцию, которую можно использовать, чтобы избежать некоторых расходов, связанных с сериализированием сообщений, содержащих неизменяемые типы. В этом разделе описывается функция и его приложение, начиная с контекста, в котором она относится.

Сериализация в Orleans

При вызове Orleans метода зерна среда выполнения создает глубокую копию аргументов метода и формирует запрос из копий. Это защищает от вызывающего кода, изменяющего объекты аргументов, прежде чем данные передаются в вызываемую зерна.

Если вызываемое зерно находится в другом сило, копии в конечном итоге сериализуются в поток байтов и отправляются по сети в целевой silo, где они десериализируются обратно в объекты. Если вызываемая зерна находится в одном и том же сило, копии передаются непосредственно в вызываемом методе.

Возвращаемые значения обрабатываются так же: сначала копируются, а затем могут быть сериализованы и десериализированы.

Обратите внимание, что все 3 процесса, копирование, сериализация и десериализация, уважение удостоверения объекта. Другими словами, если вы передаете список с одинаковым объектом в нем дважды, на принимающей стороне вы получите список с тем же объектом в нем дважды, а не с двумя объектами с одинаковыми значениями в них.

Оптимизация копирования

Во многих случаях глубокое копирование не требуется. Например, возможный сценарий — это веб-интерфейс, который получает массив байтов от своего клиента и передает этот запрос, включая массив байтов, на зерно для обработки. Интерфейсный процесс не делает ничего с массивом после того, как он передал его в зерно; В частности, он не использует массив для получения будущего запроса. Внутри зерна массив байтов анализируется для получения входных данных, но не измененных. Зерно возвращает другой массив байтов, созданный для возврата в веб-клиент; он не карта возвращает массив, как только он возвращает его. Интерфейс веб-интерфейса передает результирующий массив байтов клиенту без изменений.

В таком сценарии нет необходимости копировать массивы байтов запроса или ответа. К сожалению, среда выполнения не может понять это самостоятельно, Orleans так как она не может определить, изменяются ли массивы позже на веб-интерфейсе или с помощью зерна. В лучшем из всех возможных миров у нас был бы какой-то механизм .NET для указания того, что значение больше не изменено; Не хватает этого, мы добавили Orleansспециальные механизмы для этого: Immutable<T> класс-оболочка и класс ImmutableAttribute.

[Immutable] Используйте атрибут, чтобы сделать тип, параметр, свойство или поле неизменяемым

Для определяемых пользователем типов ImmutableAttribute можно добавить в тип. Это указывает Orleansсериализатору , чтобы избежать копирования экземпляров этого типа. Следующий фрагмент кода демонстрирует использование [Immutable] для обозначения неизменяемого типа. Этот тип не будет скопирован во время передачи.

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

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

Иногда у вас может не быть контроля над объектом, например, это может быть List<int> то, что вы отправляете между зернами. В других случаях, возможно, части ваших объектов неизменяемы и другие части не являются. В таких случаях Orleans поддерживается дополнительные параметры.

  1. Подписи методов могут включаться ImmutableAttribute на основе каждого параметра:

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Отдельные свойства и поля можно пометить как ImmutableAttribute предотвратить копирование копий при копировании экземпляров содержащего типа.

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

Использование Immutable<T>

Immutable<T> Класс-оболочка используется для указания того, что значение может считаться неизменяемым; то есть базовое значение не будет изменено, поэтому для безопасного общего доступа не требуется копирование. Обратите внимание, что использование Immutable<T> подразумевает, что ни поставщик значения, ни получатель значения не изменят его в будущем; это не односторонняя приверженность, а не взаимное двойное обязательство.

Чтобы использовать Immutable<T> в интерфейсе зерна вместо передачи, передайтеTImmutable<T>. Например, в описанном выше сценарии метод зерна:

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

Что затем станет следующим:

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

Чтобы создать Immutable<T>конструктор, просто используйте конструктор:

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

Чтобы получить значения внутри неизменяемого, используйте .Value свойство:

byte[] buffer = immutable.Value;

Неизменяемость в Orleans

Для Orleans"целей неизменяемость является довольно строгой инструкцией: содержимое элемента данных не будет изменено каким-либо образом, что может изменить семантический смысл элемента или что будет мешать другому потоку одновременно обращаться к элементу. Самый безопасный способ убедиться, что это просто не изменить элемент вообще: побитовая неизменяемость, а не логическая неизменяемость.

В некоторых случаях это безопасно для логической неизменяемости, но необходимо принять меры, чтобы обеспечить правильность мутации кода. Поскольку работа с многопоточностью сложна, и редкость в контексте Orleans , мы настоятельно рекомендуем против этого подхода и рекомендуем придерживаться битовой неизменяемости.