Partilhar via


Serialização de tipos imutáveis em Orleans

Orleans tem um recurso que pode ser usado para evitar parte da sobrecarga associada à serialização de mensagens contendo tipos imutáveis. Esta seção descreve o recurso e sua aplicação, começando com o contexto sobre onde ele é relevante.

Serialização em Orleans

Quando um método grain é invocado, o Orleans tempo de execução faz uma cópia profunda dos argumentos do método e forma a solicitação a partir das cópias. Isso protege contra o código de chamada modificando os objetos de argumento antes que os dados sejam passados 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 chamado estiver no mesmo silo, então as cópias são entregues diretamente ao método chamado.

Os valores de retorno são tratados da mesma maneira: primeiro copiados, depois possivelmente serializados e desserializados.

Observe que todos os 3 processos, cópia, serialização e desserialização, respeitam a identidade do objeto. Em outras palavras, se você passar uma lista que tenha o mesmo objeto nela duas vezes, no lado recetor você obterá uma lista com o mesmo objeto nela duas vezes, em vez de com dois objetos com os mesmos valores neles.

Otimizar a cópia

Em muitos casos, a cópia profunda é desnecessária. Por exemplo, um cenário possível é um front-end da Web que 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 de front-end não faz nada com o array depois de passá-lo para o grão; em particular, ele não reutiliza o array para receber uma solicitação futura. Dentro do grão, a matriz de bytes é analisada para buscar os dados de entrada, mas não modificada. O grão retorna outra matriz de bytes que ele criou para ser passado de volta para o web client; ele descarta a matriz assim que a retorna. 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 há necessidade de copiar as matrizes de bytes de solicitação ou resposta. Infelizmente, o Orleans tempo de execução não pode descobrir isso por si só, uma vez que não pode dizer se os arrays são modificados posteriormente pelo front-end da web ou pelo grão. No melhor dos mundos possíveis, teríamos algum tipo de mecanismo .NET para indicar que um valor não é mais modificado; Na falta disso, adicionamos Orleansmecanismos específicos para isso: a Immutable<T> classe wrapper e o ImmutableAttribute.

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

Para tipos definidos pelo usuário, o ImmutableAttribute pode ser adicionado ao tipo. Isso instrui ' Orleansserializer a evitar copiar instâncias desse tipo. O trecho de código a seguir demonstra o uso [Immutable] para denotar um tipo imutável. Este 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, você pode não ter controle sobre o objeto, por exemplo, pode ser um List<int> que você está enviando entre grãos. Outras vezes, talvez partes de seus objetos sejam imutáveis e outras não. Para estes casos, Orleans suporta opções adicionais.

  1. As assinaturas de método podem incluir ImmutableAttribute , por parâmetro:

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. Propriedades e campos individuais podem ser marcados para ImmutableAttribute impedir que cópias sejam feitas quando instâncias do tipo que 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; }
    }
    

Utilizar o comando Immutable<T>

A Immutable<T> classe wrapper é usada 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 o compartilhamento seguro. Note-se que o uso Immutable<T> implica que nem o fornecedor do valor nem o destinatário do valor irão modificá-lo no futuro, não é um compromisso unilateral, mas sim um compromisso mútuo dual-side.

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

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

Que se tornariam então:

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

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

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

Para obter os valores dentro do imutável, use a .Value propriedade:

byte[] buffer = immutable.Value;

Imutabilidade em Orleans

Para Orleans' propósitos, a imutabilidade é uma declaração bastante estrita: o conteúdo do item de dados não será modificado de qualquer forma que possa alterar o significado semântico do item, ou que interfira com outro thread acessando simultaneamente o item. A maneira mais segura de garantir isso é simplesmente não modificar o item: imutabilidade bitwise, em vez de imutabilidade lógica.

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