Inicialização lenta
Inicialização lenta de um objeto significa que a sua criação é adiada até que ele é usado pela primeira vez. (Para este tópico, os termos de a inicialização lenta e instanciação preguiçosa são sinônimos.) Inicialização lenta é usada principalmente para melhorar o desempenho, evite o desperdício computação e reduzir os requisitos de memória de programa. Estes são os cenários mais comuns:
Quando você tiver um objeto que é caro para criar e o programa não poderá usá-lo. Por exemplo, suponha que você tem na memória um Customer objeto que tem um Orders propriedade que contém uma grande variedade de Order os objetos que, ao ser inicializado, requer uma conexão de banco de dados. Não se o usuário nunca perguntar exibir os pedidos ou usar os dados em um cálculo, há nenhuma razão para usar a memória do sistema ou ciclos de computação para criá-lo. Usando Lazy<Orders> para declarar o Orders de objeto para a inicialização lenta, você pode evitar desperdiçando recursos do sistema quando o objeto não é usado.
Quando você tiver um objeto que é caro para criar e deseja adiar a sua criação até após conclusão de outras operações caras. Por exemplo, suponha que seu programa carregue várias instâncias de objeto quando ele é iniciado, mas apenas algumas delas são necessárias imediatamente. Você pode melhorar o desempenho de inicialização do programa, adiando a inicialização dos objetos que não são necessários até que os objetos necessários foram criados.
Embora seja possível escrever seu próprio código para executar a inicialização lenta, recomendamos que você use Lazy<T> em vez disso. Lazy<T>e seus tipos relacionados também oferecem suporte a segurança do thread e fornecem uma diretiva de propagação de exceção consistente.
A tabela a seguir lista os tipos que o.NET Framework versão 4 fornece para habilitar a inicialização lenta em cenários diferentes.
Tipo |
Descrição |
---|---|
Uma classe de wrapper que fornece a semântica de inicialização ociosa para qualquer biblioteca de classe ou um tipo definido pelo usuário. |
|
Semelhante a Lazy<T> , exceto que ele fornece a semântica de inicialização lenta em um local de segmento base. Cada segmento tem acesso ao seu próprio valor exclusivo. |
|
Fornece avançadas static (Shared em Visual Basic) métodos de inicialização lenta de objetos sem a sobrecarga de uma classe. |
Inicialização lenta básica
Para definir um inicializado tardiamente tipo, por exemplo, MyType, use Lazy<MyType> (Lazy(Of MyType) em Visual Basic), conforme mostrado no exemplo a seguir. Se nenhum representante é passado a Lazy<T> o tipo de quebra automática do construtor, é criado usando Activator.CreateInstance quando a propriedade value é acessada pela primeira vez. Se o tipo não tem um construtor padrão, uma exceção de tempo de execução é lançada.
O exemplo a seguir, suponha que Orders é uma classe que contém uma matriz de Order objetos recuperados a partir de um banco de dados. A Customer objeto contém uma instância de Orders, mas, dependendo das ações do usuário, os dados da Orders objeto talvez não seja necessário.
' Initialize by using default Lazy<T> constructor. The
'Orders array itself is not created yet.
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)()
// Initialize by using default Lazy<T> constructor. The
// Orders array itself is not created yet.
Lazy<Orders> _orders = new Lazy<Orders>();
Você também pode passar um representante a Lazy<T> que invoca um construtor específico do construtor no tipo disposto no momento da criação de sobrecarga e executar outras etapas de inicialização que são necessárias, conforme mostrado no exemplo a seguir.
' Initialize by invoking a specific constructor on Order
' when Value property is accessed
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)(Function() New Orders(100))
// Initialize by invoking a specific constructor on Order when Value
// property is accessed
Lazy<Orders> _orders = new Lazy<Orders>(() => new Orders(100));
Após o objeto lento não é criado, nenhuma instância de Orders é criado até o Value a propriedade da variável lenta é acessada para a primeira vez. No primeiro acesso, o tipo de quebra automática é criado e retornado e armazenado para qualquer acesso futuro.
' We need to create the array only if _displayOrders is true
If _displayOrders = True Then
DisplayOrders(_orders.Value.OrderData)
Else
' Don't waste resources getting order data.
End If
// We need to create the array only if displayOrders is true
if (displayOrders == true)
{
DisplayOrders(_orders.Value.OrderData);
}
else
{
// Don't waste resources getting order data.
}
A Lazy<T> objeto sempre retorna o mesmo objeto ou valor que ele foi inicializado com. Portanto, o Value propriedade é somente leitura. Se Value o tipo de uma referência de lojas, você não pode atribuir um novo objeto de proprietário. (No entanto, você pode alterar o valor de suas propriedades e os campos públicos definíveis). Se Value o tipo de um valor de lojas, você não pode modificar o valor. No entanto, você pode criar uma nova variável, chamando o construtor variável novamente usando novos argumentos.
_orders = New Lazy(Of Orders)(Function() New Orders(10))
_orders = new Lazy<Orders>(() => new Orders(10));
A nova instância lenta, como o anterior, não criar uma instância Orders até que seu Value propriedade é acessada pela primeira vez.
Inicialização de thread-Safe.
Conforme mencionado anteriormente, um Lazy<T> objeto sempre retorna o mesmo objeto ou valor que ele foi inicializado com, e, portanto, o Value propriedade é somente leitura. Se você habilitar o cache de exceção, este imutabilidade também estende o comportamento de exceção. Se um objeto inicializado tardiamente tem exceção cache habilitado e lança uma exceção de seu método de inicialização quando o Value propriedade é acessada pela primeira vez, essa mesma exceção é lançada em cada tentativa subseqüente para acessar o Value propriedade (ou o ToString método). Em outras palavras, o construtor do tipo empacotado é nunca re-invoked, mesmo em cenários com vários segmentos. Portanto, o Lazy<T> objeto não pode lançar uma exceção no acesso de um e retornar um valor no acesso subseqüente.
O cache de exceção é ativado quando você usar qualquer System.Lazy<T> construtor com um método de inicialização (valueFactory parâmetro); Por exemplo, ele é ativado quando você usa o Lazy(T)(Func(T)) construtor. Se o construtor também utiliza um LazyThreadSafetyMode valor (mode parâmetro), especifique LazyThreadSafetyMode.None ou LazyThreadSafetyMode.ExecutionAndPublication. Especificar um método de inicialização permite que o cache de exceções para esses dois modos. O método de inicialização pode ser muito simple. Por exemplo, ele pode chamar o construtor padrão para T: new Lazy<Contents>(() => new Contents(), mode)em C# ou New Lazy(Of Contents)(Function() New Contents()) Visual Basic. Se você usar um construtor que não especifica um método de inicialização, exceções lançadas pelo construtor padrão para T não são armazenados em cache. Para obter mais informações, consulte a enumeração LazyThreadSafetyMode.
Observação
Se você criar um Lazy<T> de objeto com o isThreadSafe parâmetro de construtor definido como false ou o mode parâmetro de construtor definido como LazyThreadSafetyMode.None, você deve acessar o Lazy<T> de objeto a partir de um único thread ou fornecer sua própria sincronização.Isso se aplica a todos os aspectos do objeto, incluindo o cache de exceções.
Conforme observado na seção anterior, Lazy<T> os objetos criados especificando LazyThreadSafetyMode.PublicationOnly tratar exceções de forma diferente. Com PublicationOnly, vários threads podem concorrer para inicializar o Lazy<T> instância. Nesse caso, as exceções não são armazenados em cache e tenta acessar a Value propriedade pode continuar até que a inicialização for bem sucedida.
A tabela a seguir resume a maneira como o Lazy<T> o cache de exceção de controle de construtores.
Construtor |
Modo de segurança do thread |
Usa o método de inicialização |
Exceções são armazenados em cache |
---|---|---|---|
Lazy(T)() |
Não |
Não |
|
Lazy(T)(func(T)) |
Sim |
Sim |
|
Lazy(T)(Boolean) |
True(ExecutionAndPublication) or false (None) |
Não |
Não |
Lazy(T)(func(T), Boolean) |
True(ExecutionAndPublication) or false (None) |
Sim |
Sim |
Lazy(T)(LazyThreadSafetyMode) |
Especificado pelo usuário |
Não |
Não |
Lazy(T)(func(T), LazyThreadSafetyMode) |
Especificado pelo usuário |
Sim |
Não se o usuário especifica PublicationOnly; Caso contrário, Sim. |
Exceções em preguiçosos objetos
Conforme mencionado anteriormente, um Lazy<T> objeto sempre retorna o mesmo objeto ou valor que ele foi inicializado com, e, portanto, o Value propriedade é somente leitura. Para os modos de segurança do thread mais comumente usadas, essa imutabilidade também estende o comportamento de exceção. Se um objeto inicializado tardiamente lança uma exceção a partir de sua lógica de inicialização quando o Value propriedade é acessada pela primeira vez, essa mesma exceção é lançada em cada tentativa subseqüente para acessar o Value propriedade. Em outras palavras, o construtor do tipo empacotado é nunca re-invoked, mesmo em cenários de vários segmentos. Portanto, uma variável lenta não pode lançar uma exceção no acesso de um e retornar um valor em um acesso subseqüente.
Observação
Se você criar um Lazy<T> objeto que tem o IsThreadSafe argumento do construtor definido como falsee então acessar o objeto de vários threads, um thread pode disparar uma exceção e outro thread pode ver um valor utilizável.Portanto, não especifique false para IsThreadSafe se há alguma chance de vários threads podem tentar acessar o objeto preguiçoso simultaneamente.
Conforme observado na seção anterior, Lazy<T> os objetos criados especificando LazyThreadSafetyMode.PublicationOnly tratar exceções de forma diferente. Com PublicationOnly, vários threads podem concorrer para inicializar o Lazy<T> instância. Nesse caso, as exceções não são armazenados em cache e tenta acessar a Value propriedade pode continuar até que a inicialização for bem sucedida.
Implementando uma propriedade inicializado tardiamente
Para implementar uma propriedade pública usando a inicialização lenta, defina o campo de apoio da propriedade como um Lazy<T>e retornar o Value propriedade a partir do get assessor de propriedade.
Class Customer
Private _orders As Lazy(Of Orders)
Public Shared CustomerID As String
Public Sub New(ByVal id As String)
CustomerID = id
_orders = New Lazy(Of Orders)(Function()
' You can specify additional
' initialization steps here
Return New Orders(CustomerID)
End Function)
End Sub
Public ReadOnly Property MyOrders As Orders
Get
Return _orders.Value
End Get
End Property
End Class
class Customer
{
private Lazy<Orders> _orders;
public string CustomerID {get; private set;}
public Customer(string id)
{
CustomerID = id;
_orders = new Lazy<Orders>(() =>
{
// You can specify any additonal
// initialization steps here.
return new Orders(this.CustomerID);
});
}
public Orders MyOrders
{
get
{
// Orders is created on first access here.
return _orders.Value;
}
}
}
O Value propriedade é somente leitura; Portanto, a propriedade que expõe tem não set acessador. Se você exigir uma propriedade de leitura/gravação, feita por um Lazy<T> objeto, o set acessador deve criar uma nova Lazy<T> object e atribuí-lo para o armazenamento de backup. O set acessador deve criar uma expressão lambda que retorna o novo valor de propriedade foi passado para o set acessador e passar essa expressão lambda para o construtor para o novo Lazy<T> objeto. O próximo acesso da Value propriedade fará com que a inicialização da nova Lazy<T>e sua Value propriedade retornará, daí em diante, o novo valor que foi atribuído à propriedade. A razão para essa organização intrincada é para preservar as proteções de multithreading embutidas no Lazy<T>. Caso contrário, os acessadores da propriedade teria em cache o primeiro valor retornado pelo Value propriedade modificar apenas o valor em cache e você teria de escrever seu próprio código de thread-safe para fazer isso. Devido às inicializações adicionais necessárias para uma propriedade de leitura/gravação com o respaldo de uma Lazy<T> o objeto, o desempenho pode não ser aceitável. Além disso, dependendo do cenário específico, pode ser necessário coordenação adicional para evitar condições de corrida entre setters e getters.
Local de segmento inicialização lenta
Em alguns cenários multithread, convém dar seus próprios dados particulares de cada segmento. Esses dados são chamados de dados do local de segmento. No.NET Framework versão 3.5 e versões anterior, você poderia aplicar o ThreadStatic de atributo para uma variável estática para torná-lo thread-local. No entanto, usando o ThreadStatic atributo pode levar a erros sutis. Por exemplo, instruções de inicialização básica ainda fará com que a variável ser inicializados somente no primeiro segmento que o acessa, conforme mostrado no exemplo a seguir.
<ThreadStatic()>
Shared counter As Integer
[ThreadStatic]
static int counter = 1;
Em outros segmentos, a variável será inicializada usando o seu valor padrão (zero). Como alternativa na.NET Framework versão 4, você pode usar o System.Threading.ThreadLocal<T> tipo para criar uma variável de segmento local, baseado em instância é inicializada em todos os threads, o Action<T> delegado que você fornecer. No exemplo a seguir, todos os threads que acesse counter verá seu valor inicial como 1.
Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)
ThreadLocal<int> betterCounter = new ThreadLocal<int>(() => 1);
ThreadLocal<T>empacota seu objeto da mesma forma como Lazy<T>, com essas diferenças essenciais:
Cada thread inicializa a variável de segmento local usando seus próprios dados privados que não estão acessíveis a partir de outros segmentos.
O ThreadLocal<T>.Value propriedade é leitura-gravação e pode ser modificada a qualquer número de vezes. Isso pode afetar a propagação de exceção, por exemplo, um get operação pode elevar uma exceção, mas com êxito pode inicializar o próximo valor.
Se nenhum delegado de inicialização for fornecido, ThreadLocal<T> irá inicializar o seu tipo de quebra automática usando o valor padrão do tipo. Nesse aspecto, ThreadLocal<T> é consistente com o ThreadStaticAttribute atributo.
O exemplo a seguir demonstra que todos os threads que acessa o ThreadLocal<int> instância obtém sua própria cópia única de dados.
' Initialize the integer to the managed thread id on a per-thread basis.
Dim threadLocalNumber As New ThreadLocal(Of Integer)(Function() Thread.CurrentThread.ManagedThreadId)
Dim t4 As New Thread(Sub()
Console.WriteLine("number on t4 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t4.Start()
Dim t5 As New Thread(Sub()
Console.WriteLine("number on t5 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t5.Start()
Dim t6 As New Thread(Sub()
Console.WriteLine("number on t6 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t6.Start()
' Ensure that thread IDs are not recycled if the
' first thread completes before the last one starts.
t4.Join()
t5.Join()
t6.Join()
'Sample(Output)
' threadLocalNumber on t4 = 14 ThreadID = 14
' threadLocalNumber on t5 = 15 ThreadID = 15
' threadLocalNumber on t6 = 16 ThreadID = 16
// Initialize the integer to the managed thread id on a per-thread basis.
ThreadLocal<int> threadLocalNumber = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId);
Thread t4 = new Thread(() => Console.WriteLine("threadLocalNumber on t4 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t4.Start();
Thread t5 = new Thread(() => Console.WriteLine("threadLocalNumber on t5 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t5.Start();
Thread t6 = new Thread(() => Console.WriteLine("threadLocalNumber on t6 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t6.Start();
// Ensure that thread IDs are not recycled if the
// first thread completes before the last one starts.
t4.Join();
t5.Join();
t6.Join();
/* Sample Output:
threadLocalNumber on t4 = 14 ThreadID = 14
threadLocalNumber on t5 = 15 ThreadID = 15
threadLocalNumber on t6 = 16 ThreadID = 16
*/
Variáveis de segmento locais na Parallel. for e ForEach
Quando você usa o Parallel.For método ou Parallel.ForEach método para iterar em fontes de dados em paralelo, você pode usar os métodos sobrecarregados que possuem suporte interno para o local de segmento de dados. Esses métodos, a localidade do thread é conseguida usando representantes locais para criar, acessar e limpar os dados. Para obter mais informações, consulte Como: Criar um Loop de Parallel tem variáveis de segmento locais e Como: Criar um Loop de Parallel tem variáveis de segmento locais.
Usando a inicialização lenta para cenários de baixa sobrecarga
Em situações onde você precise preguiçoso inicializar um grande número de objetos, você pode decidir que a disposição de cada objeto em um Lazy<T> requer muita memória ou muitos recursos de computação. Ou, talvez você tenha a rigorosos requisitos de inicialização como lenta é exposto. Em tais casos, você pode usar o static (Shared em Visual Basic) métodos da System.Threading.LazyInitializer classe lazy initialize cada objeto sem encapsulá-la em uma instância de Lazy<T>.
O exemplo a seguir, suponha que, em vez de encapsular toda uma Orders objeto em um Lazy<T> o objeto, você tem o indivíduo inicializado tardiamente Order objetos somente se forem necessários.
' Assume that _orders contains null values, and
' we only need to initialize them if displayOrderInfo is true
If displayOrderInfo = True Then
For i As Integer = 0 To _orders.Length
' Lazily initialize the orders without wrapping them in a Lazy(Of T)
LazyInitializer.EnsureInitialized(_orders(i), Function()
' Returns the value that will be placed in the ref parameter.
Return GetOrderForIndex(i)
End Function)
Next
End If
// Assume that _orders contains null values, and
// we only need to initialize them if displayOrderInfo is true
if(displayOrderInfo == true)
{
for (int i = 0; i < _orders.Length; i++)
{
// Lazily initialize the orders without wrapping them in a Lazy<T>
LazyInitializer.EnsureInitialized(ref _orders[i], () =>
{
// Returns the value that will be placed in the ref parameter.
return GetOrderForIndex(i);
});
}
}
Neste exemplo, observe que o procedimento de inicialização é chamado em cada iteração do loop. Em cenários de vários segmentos, o primeiro thread para chamar o procedimento de inicialização é aquele cujo valor é visto por todos os threads. Segmentos posteriores também chamar o procedimento de inicialização, mas seus resultados não são usados. Se esse tipo de condição de corrida potencial não for aceitável, use a sobrecarga de LazyInitializer.EnsureInitialized que leva um argumento booleano e um objeto de sincronização.
Consulte também
Tarefas
Como: Executar inicialização lenta de objetos
Conceitos
Biblioteca paralela de tarefas
Outros recursos
Noções básicas de threads gerenciadas
Histórico de alterações
Date |
History |
Motivo |
---|---|---|
Março de 2011 |
Corrigido informações sobre o cache de exceção. |
Correção de bug de conteúdo. |