Compartilhar via


Serialização de tipos imutáveis em Orleans

Orleans tem um recurso que você pode usar para evitar alguma sobrecarga associada à serialização de mensagens que contêm tipos imutáveis. Esta seção descreve o recurso e seu aplicativo, começando com o contexto em que ele é relevante.

Serialização em Orleans

Quando você invoca um método de granulação, o Orleans runtime faz uma cópia profunda dos argumentos do método e forma a solicitação dessas cópias. Isso protege contra o código de chamada que modifica os objetos de argumento antes que os dados passem para o grão chamado.

Se o grão chamado estiver em um silo diferente, as cópias serão eventualmente serializadas em um fluxo de bytes e enviadas pela rede para o silo de destino, onde serão desserializadas novamente em objetos. Se o grão em questão estiver no mesmo silo, as cópias serão passadas diretamente ao método chamado.

Os valores retornados são tratados da mesma maneira: primeiro copiados e, em seguida, possivelmente serializados e desserializados.

Observe que todos os três processos — copiando, serializando e desserializando — respeitam a identidade do objeto. Em outras palavras, se você passar uma lista contendo o mesmo objeto duas vezes, o lado receptor receberá uma lista com o mesmo objeto duas vezes, em vez de dois objetos com os mesmos valores.

Otimizar a cópia de dados

Em muitos casos, a cópia profunda é desnecessária. Por exemplo, considere um cenário em que um front-end da Web recebe uma matriz de bytes de seu cliente e passa essa solicitação, incluindo a matriz de bytes, para um grão para processamento. O processo front-end não faz nada com a matriz depois de passá-la para o grão; especificamente, ele não reutiliza a matriz para solicitações futuras. Dentro do grão, a matriz de bytes é analisada para buscar dados de entrada, mas não é modificada. O grão retorna outra matriz de bytes que ele criou de volta para o cliente Web e descarta a matriz imediatamente depois de devolvê-la. O front-end da Web passa a matriz de bytes de resultado de volta para seu cliente sem modificação.

Nesse cenário, não é necessário copiar as matrizes de bytes de solicitação ou resposta. Infelizmente, o Orleans runtime não pode descobrir isso automaticamente, pois não pode determinar se o front-end da Web ou o grão modifica as matrizes mais tarde. O ideal é que um mecanismo .NET indique que um valor não é mais modificado. Sem isso, adicionamos mecanismos específicos ao Orleans: a classe wrapper Immutable<T> e o ImmutableAttribute.

Use o [Immutable] atributo para marcar um tipo, parâmetro, propriedade ou campo como imutável

Para tipos definidos pelo usuário, você pode adicionar o ImmutableAttribute ao tipo. Isso orienta o serializador Orleans a evitar copiar instâncias desse tipo. O snippet de código a seguir demonstra o uso [Immutable] para denotar um tipo imutável. Esse tipo não será copiado durante a transmissão.

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

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

Às vezes, talvez você não controle o objeto; por exemplo, pode ser um List<int> envio entre grãos. Outras vezes, partes de seus objetos podem ser imutáveis, enquanto outras não. Para esses casos, Orleans dá suporte a opções adicionais.

  1. As assinaturas de método podem incluir ImmutableAttribute para cada parâmetro:

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Marque propriedades e campos individuais como ImmutableAttribute para evitar cópias quando as instâncias do tipo que os contém são copiadas.

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

Utilize Immutable<T>

Use a Immutable<T> classe wrapper para indicar que um valor pode ser considerado imutável; ou seja, o valor subjacente não será modificado, portanto, nenhuma cópia é necessária para compartilhamento seguro. Observe que o uso Immutable<T> não implica que nem o provedor nem o destinatário do valor o modificarão no futuro. É um compromisso mútuo, de dois lados, não unilateral.

Para usar Immutable<T> em sua interface de grãos, passe Immutable<T> em vez de T. Por exemplo, no cenário descrito acima, o método de grãos era:

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

Que então se tornaria:

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

Para criar um Immutable<T>, basta usar seu construtor:

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

Para obter o valor dentro do wrapper imutável, use a propriedade .Value.

byte[] buffer = immutable.Value;

Imutabilidade no Orleans

Para os fins de Orleans, a imutabilidade é uma instrução estrita: os conteúdos do item de dados não serão modificados de forma alguma que possa alterar o significado semântico do item ou interferir em outra thread que o esteja acessando simultaneamente. A maneira mais segura de garantir isso é simplesmente não modificar o item: usar imutabilidade bit a bit em vez de imutabilidade lógica.

Em alguns casos, é seguro relaxar isso para a imutabilidade lógica, mas você deve ter cuidado para garantir que o código mutável seja adequadamente thread-safe. Como lidar com multithreading é complexo e incomum em um Orleans contexto, recomendamos fortemente contra essa abordagem e aconselhamos manter a imutabilidade bitwise.