Este artigo foi traduzido por máquina.

Cutting Edge

Um pouco de preguiça não faz mal a ninguém

Dino Esposito

Dino EspositoDesenvolvimento de software, preguiça a termo se refere ao atrasar determinada atividade cara tanto quanto possível muito mais do que ela está relacionada à inatividade. Software preguiça é ainda sobre como fazer as coisas, mas isso significa que qualquer ação ocorrerá somente quando for necessário para concluir uma determinada tarefa. Nesse aspecto, preguiça é um padrão importante no desenvolvimento de software e pode ser aplicada com êxito a uma variedade de cenários, incluindo o design e implementação.

Por exemplo, uma das práticas de codificação fundamentais da metodologia Extreme Programming resumida simplesmente como “ É não são Gonna precisa de IT, ” que é um convite explícito seja preguiçoso e incorporar somente os recursos que você precisa da Base de código — e somente quando precisar deles.

Em uma nota de diferente durante a implementação de uma classe, você pode desejar ser lenta quando se trata de carregamento de dados de uma fonte que não é tão barata acessar. O padrão de carregamento lento, na verdade, ilustra a solução comumente aceita de ter um membro de uma classe definida mas mantido vazio até que seu conteúdo, na verdade, é exigido por algum outro código de cliente. Carregamento lento se encaixa perfeitamente no contexto das ferramentas de mapeamento relacional de objeto (ORM) como, por exemplo, Estrutura de Entidade e NHibernate. Ferramentas ORM são usadas para mapear as estruturas de dados entre o mundo orientado a objeto e o banco de dados relacionais. Nesse contexto, o carregamento lento se refere a capacidade de uma estrutura para carregar, para
exemplo, pedidos para um cliente somente quando alguns códigos é a tentativa de ler a propriedade de coleção de Orders exposta em uma classe de atendimento ao cliente.

Carregando preguiça, no entanto, não está limitado a cenários de implementação específica, como a programação de ORM. Além disso, o carregamento lento é sobre não obtendo uma instância de alguns dados antes de realmente torna útil. Em outras palavras, carregamento lento é sobre a necessidade de lógica de fábrica especial que controla o que tem a ser criado e cria silenciosamente quando o conteúdo, por fim, é solicitado.

No Microsoft .NET Framework, os desenvolvedores muito tivemos que implementar qualquer comportamento preguiçoso manualmente em nossas classes. Nunca houve maquinaria interna para ajudar nesta tarefa. Não até o advento do .NET Framework 4, ou seja, em que podemos começar a
utilizando a nova classe Lazy <T>.

Cumprir o Monitor de <T> de lento pol Class

O lazy <T> é uma fábrica especial que você usa como um wrapper em torno de um objeto de um determinado tipo t. O wrapper lazy <T> representa um proxy em tempo real para uma instância da classe que ainda não existe. Há muitas razões para usar esses invólucros preguiçosos, o mais importante é melhorar o desempenho. Com a inicialização lenta de objetos, você evita qualquer computação que não seja estritamente necessário, para reduzir o consumo de memória. Se aplicada corretamente, preguiça instanciação de objetos também pode ser uma ferramenta formidável para tornar os aplicativos muito mais rápida de iniciar. O código a seguir mostra como inicializar um objeto de forma lenta:

var container = new Lazy<DataContainer>();

Neste exemplo, a classe DataContainer indica que um objeto de recipiente de dados sem formatação que faz referência a matrizes de outros objetos. Logo após chamar o novo operador em uma instância de lazy <T>, você só precisa fazer é uma instância em tempo real da classe Lazy <T>; nenhum caso, você terá uma instância do tipo especificado t. Se você precisar passar uma instância de DataContainer aos membros de outras classes, você deve alterar a assinatura desses membros usar lazy <DataContainer> como este:

void ProcessData(Lazy<DataContainer> container);

Quando a instância real da DataContainer é criada para que o programa possa trabalhar com os dados necessários? Let’s dê uma olhada na interface de programação pública da classe Lazy <T>. A interface pública é relativamente fina que ele inclua apenas duas propriedades: Valor e IsValue ­ de criação. A propriedade Value retorna o valor atual da instância associada com o tipo de lento, se houver algum. A propriedade é definida da seguinte maneira:

public T Value 
{
  get { ... }
}

A propriedade IsValueCreated retorna um valor Boolean e indica se o tipo lento foi instanciado tem. Este é um excerto do seu código-fonte:

public bool IsValueCreated
{
  get
  {
    return ((m_boxed != null) && (m_boxed is Boxed<T>));
  }
}

O membro m_boxed é um membro interno de voláteis e particular da classe Lazy <T> que contém a instância real do tipo T, se houver algum. Portanto, IsValueCreated simplesmente verifica se uma instância dinâmica de T existe e retorna uma resposta booleana. Conforme mencionado, o membro m_boxed é particular e voláteis, como mostrado neste trecho:

private volatile object m_boxed;

No c#, a palavra-chave volatile indica um membro pode ser modificado por um segmento ao mesmo tempo em execução. A palavra-chave volátil será usada para membros que estão disponíveis em um ambiente com vários threads, mas falta (basicamente por razões de desempenho) qualquer proteção contra possíveis segmentos simultâneos que poderia acessar esses membros ao mesmo tempo. Voltarei a segmentação aspectos da lazy <T> posteriormente. Por enquanto, é suficiente dizer que os membros públicos e protegidos lazy <T> são thread-safe por padrão. A instância real do tipo T é criada na primeira vez que qualquer código tenta acessar o valor de membro. Os detalhes da criação do objeto dependem de atributos threads opcionalmente especificados por meio do construtor lazy <T>. Ele deve estar claro que as implicações do modo de encadeamento só são importantes para quando o valor convertidos, na verdade, é inicializado ou acessado pela primeira vez.

No caso padrão, uma instância do tipo T é obtida por meio de reflexão, colocando uma chamada para Activator.CreateInstance. Aqui está um exemplo rápido da interação com o tipo de lazy <T> típico:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.IsValueCreated);
Console.WriteLine(temp.Value.SomeValue);

Observe que não é estritamente necessário para verificar IsValueCreated antes de invocar o valor. Você normalmente recorrer a verificar o valor do IsValueCreated somente se – por algum motivo — você precisa saber se um valor é atualmente associado ao tipo lento. Não é necessário para que você verifique IsValueCreated para evitar uma exceção de referência nula no valor. O código a seguir funciona muito bem:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.Value.SomeValue);

O getter de propriedade Value verifica se já existe um valor convertidos; caso contrário, ele aciona a lógica que cria uma instância do tipo de quebra automática e retorna que.

O processo de instanciação

Obviamente, se digitar o construtor da lazy — DataContainer no exemplo anterior — lança uma exceção, seu código é responsável pela manipulação essa exceção. A exceção capturada é do tipo TargetInvocationException — a exceção típica obtido quando a falha de reflexão do .net criar uma instância de um tipo indiretamente.

A lógica de wrapper lazy <T> simplesmente garante que uma instância do tipo T é criada; de nenhuma forma ele também garante que você não receberá uma exceção de referência nula como acessar qualquer um dos membros públicos em t. Por exemplo, considere o trecho de código a seguir:

public class DataContainer
{
  public DataContainer()
  {
  }

  public IList<String> SomeValues { get; set; }
}

Imagine agora que você tentar chamar o código a seguir a partir de um programa cliente:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.Value.SomeValues.Count);

Nesse caso, você irá receber uma exceção porque a propriedade SomeValues do objeto DataContainer é nula, não porque a DataContainer é nulo propriamente dito. A exceção dispara, porque o construtor do DataContainer não inicializar todos os seus membros corretamente; o erro não tem nada a ver com a implementação do método lento.

A propriedade Value do lazy <T> é uma propriedade somente leitura, significando que uma vez inicializado, um objeto lazy <T> sempre retorna a mesma instância do tipo T ou o mesmo valor se T é um tipo de valor. Não é possível modificar a instância, mas você pode acessar as propriedades públicas que a instância pode ter.

Aqui está como você pode configurar um objeto lazy <T> para passar parâmetros de ad hoc para o tipo de T:

temp = new Lazy<DataContainer>(() => new Orders(10));

Um dos construtores lazy <T> aceita um delegado através do qual você pode especificar qualquer ação necessária para produzir os dados de entrada adequados para o construtor de T. O delegado não será executado até que a propriedade Value do tipo T com quebra automática é acessada pela primeira vez.

Inicialização de thread-safe.

Por padrão, lazy <T> é thread-safe, o que significa que vários threads podem acessar um objeto e todos os threads irão receber a mesma instância
do tipo T. Olhe Let’s aspectos de segmentação que são importantes para o primeiro acesso a um objeto lento.

O primeiro thread a ocorrência do objeto lazy <T> irá disparar o processo de instanciação do tipo t. Todos os threads seguintes que acessem o valor de recebem a resposta gerada pela primeira — tudo o que é. Em outras palavras, se o primeiro thread causa uma exceção ao chamar o construtor do tipo T, em seguida, todas as chamadas subseqüentes — independentemente do segmento, receberá a mesma exceção.
Por design, diversos threads não é possível obter respostas diferentes da mesma instância do lazy <T>. Esse é o comportamento que você obtém quando escolhe o construtor padrão do lazy <T>.

A classe Lazy <T>, no entanto, também possui um construtor adicional:

public Lazy(bool isThreadSafe)

O argumento booleano indica se deseja ou não segurança do thread. Conforme mencionado, o valor padrão é true, o que irá oferecer o comportamento mencionado anteriormente.

Se você passar Falso de em vez disso, a propriedade Value será acessada de apenas um thread — aquele que inicializa o tipo lento. Se vários segmentos tentarem acessar a propriedade Value, o comportamento é indefinido.

O construtor lazy <T> que aceita um valor booleano é um caso especial de uma assinatura mais geral em que você passar o lazy <T>
um valor de enumeração LazyThreadSafetyMode do construtor. A Figura 1 explica a função de cada valor na enumeração.

Figura 1 do TheLazyThreadSafetyMode Enumeration

Valor Descrição
Nenhuma A instância lazy <T> não é thread-safe e seu comportamento não está definido se ele é acessado a partir de vários threads.
PublicationOnly Vários threads podem simultaneamente tentar inicializar o tipo lento. O primeiro thread para concluir o wins e os resultados gerados por todos os outros serão descartados.
ExecutionAndPublication Bloqueios são usados para garantir que somente um único thread pode inicializar uma instância de lazy <T> de maneira segura para thread.

Você pode definir o modo de PublicationOnly usando qualquer um dos construtores a seguintes:

public Lazy(LazyThreadSafetyMode mode)
public Lazy<T>(Func<T>, LazyThreadSafetyMode mode)

Os valores de 1 Figura diferente PublicationOnly são definidos implicitamente quando você usa o construtor que aceita um valor Boolean:

public Lazy(bool isThreadSafe)

Nesse construtor se isThreadSafe o argumento for falso, em seguida, o modo de segmento selecionado é nenhum. Se o isThreadSafe argumento estiver definido como Verdadeiro de , o modo de segmento é definido para ExecutionAndPublication. ExecutionAndPublication também é o modo de trabalho quando você escolhe o construtor padrão.

O modo PublicationOnly encontra-se em algum lugar entre a segurança do thread total garantida por ExecutionAndPublication e a ausência dele que você obtém com nenhum. PublicationOnly permite que segmentos simultâneos tentar criar a instância do tipo T, mas garante que apenas um thread é a vencedora. A instância T criada pelo vencedor é compartilhada entre todos os outros threads, independentemente da instância que cada um pode ser computadas.

Há uma diferença interessante entre nenhum e ExecutionAndPublication sobre uma possível exceção lançada durante a inicialização. Quando PublicationOnly for definida, uma exceção gerada durante a inicialização não está armazenada em cache; depois disso, cada thread tenta ler o valor terá a oportunidade de reinicializar, se uma instância do T não estiver disponível. Outra diferença entre PublicationOnly e nenhum é nenhuma exceção é lançada no modo de PublicationOnly se o construtor de T as tentativas de acesso de forma recursiva Value. Essa situação irá disparar uma exceção InvalidOperation quando a classe Lazy <T> funciona em nenhuma ou ExecutionAndPublication modos.

Descartando a segurança do thread oferece uma vantagem de desempenho bruto, mas você precisa ter cuidado para evitar bugs desagradáveis e condições de corrida. Por isso, recomendo que você usar a opção LazyThreadSafetyMode.None somente quando o desempenho é extremamente importante.

Se você usar LazyThreadSafetyMode.None, ele permanece sua responsabilidade é garantir que a instância lazy <T> nunca será inicializada a partir de mais de um thread. Caso contrário, você poderá incorrer em resultados imprevisíveis. Se uma exceção for gerada durante a inicialização, a mesma exceção é gerada para cada acesso subseqüente ao valor no mesmo thread e armazenados em cache.

Variável de inicialização ThreadLocal

Por design, lazy <T> não permite que diferentes threads gerenciar sua própria instância particular do tipo t. No entanto, se você quiser permitir que
comportamento, que você deve optar por uma classe diferente — o tipo de ThreadLocal <T>. Aqui está como usá-lo:

var counter = new ThreadLocal<Int32>(() => 1);

O construtor utiliza um delegado e a usa para inicializar a variável de local de segmento. Cada segmento possui seus próprios dados que está completamente fora do alcance de outros threads. Ao contrário de lazy <T>, a propriedade Value em ThreadLocal <T> é leitura / gravação. Cada acesso, portanto, é independente do próximo e poderá produzir resultados diferentes, incluindo lançar (ou não) uma exceção. Se você Don fornecer um delegado de ação por meio do construtor ThreadLocal <T>, o objeto incorporado é inicializado usando o valor padrão para o tipo — nulo se T é uma classe.

Implementando propriedades lentas

Na maioria das vezes, você usa lazy <T> para propriedades em suas próprias classes, mas as classes que exatamente? Ferramentas de ORM oferecem carregamento lento por conta própria, portanto, se você estiver usando essas ferramentas, o acesso a dados camada provavelmente não o segmento do aplicativo onde você encontrará candidato provável classes para propriedades de carregamentos do host. Se você não estiver usando ferramentas ORM, a camada de acesso a dados é definitivamente uma boa opção para propriedades preguiçosos.

Segmentos do aplicativo em que você usa a injeção de dependência podem ser outra boa opção para preguiça. No .NET Framework 4, o Managed Extensibility Framework (MEF) implementa apenas a extensibilidade e inversão de controle usando lazy <T>. Mesmo se não estiver usando o MEF diretamente, o gerenciamento de dependências é uma excelente opção para propriedades preguiçosos.

Uma propriedade preguiça dentro de uma classe de implementação não requer qualquer ciência de foguete, como demonstra a do Figura 2.

Do exemplo de uma propriedade lento, a Figura 2

public class Customer
{
   private readonly Lazy<IList<Order>> orders;

   public Customer(String id)
   {
      orders = new Lazy<IList<Order>>( () =>
      {
         return new List<Order>();
      }
      );
   }

   public IList<Order> Orders
   {
      get
      {
         // Orders is created on first access
         return orders.Value;
      }
   }
}

Preencher um círculo

Quebra automática de, carregamento lento é um conceito abstrato que se refere a carregar dados somente quando ela for realmente necessária.Até que o .NET Framework 4, os desenvolvedores necessária para cuidar de desenvolvimento de lógica de inicialização ociosa propriamente ditas.A classe Lazy <T> estende o Toolkit de programação do .NET Framework e oferece uma ótima oportunidade para evitar o desperdício da computação pela instanciação de objetos de caros somente quando estritamente necessário, e começa a apenas um pouco antes de seu uso.

Dino Esposito é o autor de “Programming ASP.NET MVC" da Microsoft Press e coautor de Microsoft .NET: Arquitetura de aplicativos para a empresa  (Microsoft Press, 2008). Residente na Itália, Esposito é um palestrante sempre presente em eventos do setor no mundo inteiro. Consulte seu blog em weblogs.asp.net/despos.

Agradeço ao seguinte especialista técnico pela revisão deste artigo: Greg Paperin