Inicialización diferidaLazy Initialization

La inicialización diferida de un objeto implica que su creación se aplaza hasta que se usa por primera vez.Lazy initialization of an object means that its creation is deferred until it is first used. (En este tema, los términos inicialización diferida y creación de instancias diferidas son sinónimos). La inicialización diferida se utiliza principalmente para mejorar el rendimiento, evitar el cálculo excesivo y reducir los requisitos de memoria del programa.(For this topic, the terms lazy initialization and lazy instantiation are synonymous.) Lazy initialization is primarily used to improve performance, avoid wasteful computation, and reduce program memory requirements. Estos son los escenarios más comunes:These are the most common scenarios:

  • Cuando hay un objeto costoso de crear y es posible que el programa no lo use.When you have an object that is expensive to create, and the program might not use it. Por ejemplo, supongamos que tiene en memoria un objeto Customer con una propiedad Orders que contiene una matriz grande de objetos Order que, para inicializarse, requieren una conexión de base de datos.For example, assume that you have in memory a Customer object that has an Orders property that contains a large array of Order objects that, to be initialized, requires a database connection. Si el usuario nunca solicita que se muestre Orders y nunca usa los datos en un cálculo, no hay ninguna razón para usar la memoria del sistema o ciclos de cálculos para crearlo.If the user never asks to display the Orders or use the data in a computation, then there is no reason to use system memory or computing cycles to create it. Mediante el uso de Lazy<Orders> para declarar el objeto Orders para la inicialización diferida, puede evitar desperdiciar recursos del sistema si no se usa el objeto.By using Lazy<Orders> to declare the Orders object for lazy initialization, you can avoid wasting system resources when the object is not used.

  • Cuando hay un objeto costoso de crear y quiere diferir su creación hasta después de que se hayan completado otras operaciones costosas.When you have an object that is expensive to create, and you want to defer its creation until after other expensive operations have been completed. Por ejemplo, supongamos que el programa carga varias instancias de objeto cuando se inicia, pero solo se necesitan de inmediato algunas de ellas.For example, assume that your program loads several object instances when it starts, but only some of them are required immediately. Puede mejorar el rendimiento de inicio del programa si difiere la inicialización de los objetos que no son necesarios hasta que se hayan creado los objetos necesarios.You can improve the startup performance of the program by deferring initialization of the objects that are not required until the required objects have been created.

Aunque puede escribir su propio código para llevar a cabo la inicialización diferida, recomendamos que use Lazy<T> en su lugar.Although you can write your own code to perform lazy initialization, we recommend that you use Lazy<T> instead. Lazy<T> y sus tipos relacionados también admiten la seguridad para subprocesos y ofrecen una directiva coherente de propagación de excepciones.Lazy<T> and its related types also support thread-safety and provide a consistent exception propagation policy.

En la tabla siguiente se muestran los tipos que .NET Framework versión 4 proporciona para habilitar la inicialización diferida en distintos escenarios.The following table lists the types that the .NET Framework version 4 provides to enable lazy initialization in different scenarios.

TipoType DescripciónDescription
Lazy<T> Clase contenedora que proporciona semántica de inicialización diferida para cualquier biblioteca de clases o tipo definido por el usuario.A wrapper class that provides lazy initialization semantics for any class library or user-defined type.
ThreadLocal<T> Se parece a Lazy<T>, con la diferencia de que proporciona semántica de inicialización diferida para cada subproceso local.Resembles Lazy<T> except that it provides lazy initialization semantics on a thread-local basis. Cada subproceso tiene acceso a su propio valor único.Every thread has access to its own unique value.
LazyInitializer Proporciona métodos static avanzados (Shared en Visual Basic) para la inicialización diferida de objetos sin la sobrecarga de una clase.Provides advanced static (Shared in Visual Basic) methods for lazy initialization of objects without the overhead of a class.

Inicialización diferida básicaBasic Lazy Initialization

Para definir un tipo con inicialización diferida, como MyType, use Lazy<MyType> (Lazy(Of MyType) en Visual Basic), como se muestra en el ejemplo siguiente.To define a lazy-initialized type, for example, MyType, use Lazy<MyType> (Lazy(Of MyType) in Visual Basic), as shown in the following example. Si no se pasa ningún delegado en el constructor Lazy<T>, el tipo contenedor se crea mediante Activator.CreateInstance cuando se tiene acceso por primera vez a la propiedad de valor.If no delegate is passed in the Lazy<T> constructor, the wrapped type is created by using Activator.CreateInstance when the value property is first accessed. Si el tipo no tiene un constructor sin parámetros, se produce una excepción en tiempo de ejecución.If the type does not have a parameterless constructor, a run-time exception is thrown.

En el ejemplo siguiente, supongamos que Orders es una clase que contiene una matriz de objetos Order recuperados de una base de datos.In the following example, assume that Orders is a class that contains an array of Order objects retrieved from a database. Un objeto Customer contiene una instancia de Orders, pero en función de las acciones del usuario, los datos del objeto Orders podrían no ser necesarios.A Customer object contains an instance of Orders, but depending on user actions, the data from the Orders object might not be required.

// Initialize by using default Lazy<T> constructor. The
// Orders array itself is not created yet.
Lazy<Orders> _orders = new Lazy<Orders>();
' 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)()

También se puede pasar un delegado en el constructor Lazy<T> que invoca una sobrecarga de constructor específica en el tipo ajustado en tiempo de creación y realizar los pasos de inicialización que se requieran, como se muestra en el ejemplo siguiente.You can also pass a delegate in the Lazy<T> constructor that invokes a specific constructor overload on the wrapped type at creation time, and perform any other initialization steps that are required, as shown in the following example.

// Initialize by invoking a specific constructor on Order when Value
// property is accessed
Lazy<Orders> _orders = new Lazy<Orders>(() => new Orders(100));
' 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))

Una vez que se ha creado el objeto diferido, no se crea ninguna instancia de Orders mientras no se tenga acceso por primera vez a la propiedad Value de la variable Lazy.After the Lazy object is created, no instance of Orders is created until the Value property of the Lazy variable is accessed for the first time. En el primer acceso, el tipo encapsulado se crea y se devuelve, y se almacena para cualquier acceso futuro.On first access, the wrapped type is created and returned, and stored for any future access.

// 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.
}
' 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

Un objeto Lazy<T> siempre devuelve el mismo objeto o valor con el que se ha inicializado.A Lazy<T> object always returns the same object or value that it was initialized with. Por lo tanto, la propiedad Value es de solo lectura.Therefore, the Value property is read-only. Si Value almacena un tipo de referencia, no se le puede asignar un nuevo objeto.If Value stores a reference type, you cannot assign a new object to it. (Sin embargo, puede cambiar el valor de sus propiedades y campos públicos configurables). Si Value almacena un tipo de valor, no se puede modificar su valor.(However, you can change the value of its settable public fields and properties.) If Value stores a value type, you cannot modify its value. Aun así, puede crear una variable si invoca de nuevo el constructor de la variable con argumentos nuevos.Nevertheless, you can create a new variable by invoking the variable constructor again by using new arguments.

_orders = new Lazy<Orders>(() => new Orders(10));
_orders = New Lazy(Of Orders)(Function() New Orders(10))

La nueva instancia diferida, al igual que la anterior, no crea instancias de Orders mientras no se tenga acceso por primera vez a su propiedad Value.The new lazy instance, like the earlier one, does not instantiate Orders until its Value property is first accessed.

Inicialización segura para subprocesosThread-Safe Initialization

De forma predeterminada, los objetos Lazy<T> son seguros para subprocesos.By default, Lazy<T> objects are thread-safe. Es decir, si el constructor no especifica el tipo de seguridad para subprocesos, los objetos Lazy<T> que crea son seguros para subprocesos.That is, if the constructor does not specify the kind of thread safety, the Lazy<T> objects it creates are thread-safe. En escenarios multiproceso, el primer subproceso que tiene acceso a la propiedad Value de un objeto Lazy<T> seguro para subprocesos lo inicializa para todos los accesos siguientes en todos los subprocesos, y todos los subprocesos comparten los mismos datos.In multi-threaded scenarios, the first thread to access the Value property of a thread-safe Lazy<T> object initializes it for all subsequent accesses on all threads, and all threads share the same data. Por lo tanto, no importa qué subproceso inicializa el objeto y las condiciones de carrera son benignas.Therefore, it does not matter which thread initializes the object, and race conditions are benign.

Nota

Puede ampliar esta coherencia a las condiciones de error mediante el uso del almacenamiento en caché de excepciones.You can extend this consistency to error conditions by using exception caching. Para obtener más información, vea la próxima sección titulada Excepciones en objetos diferidos.For more information, see the next section, Exceptions in Lazy Objects.

En el ejemplo siguiente se muestra que la instancia Lazy<int> tiene el mismo valor para tres subprocesos independientes.The following example shows that the same Lazy<int> instance has the same value for three separate threads.

// Initialize the integer to the managed thread id of the
// first thread that accesses the Value property.
Lazy<int> number = new Lazy<int>(() => Thread.CurrentThread.ManagedThreadId);

Thread t1 = new Thread(() => Console.WriteLine("number on t1 = {0} ThreadID = {1}",
                                        number.Value, Thread.CurrentThread.ManagedThreadId));
t1.Start();

Thread t2 = new Thread(() => Console.WriteLine("number on t2 = {0} ThreadID = {1}",
                                        number.Value, Thread.CurrentThread.ManagedThreadId));
t2.Start();

Thread t3 = new Thread(() => Console.WriteLine("number on t3 = {0} ThreadID = {1}", number.Value,
                                        Thread.CurrentThread.ManagedThreadId));
t3.Start();

// Ensure that thread IDs are not recycled if the
// first thread completes before the last one starts.
t1.Join();
t2.Join();
t3.Join();

/* Sample Output:
    number on t1 = 11 ThreadID = 11
    number on t3 = 11 ThreadID = 13
    number on t2 = 11 ThreadID = 12
    Press any key to exit.
*/
' Initialize the integer to the managed thread id of the 
' first thread that accesses the Value property.
Dim number As Lazy(Of Integer) = New Lazy(Of Integer)(Function()
                                                          Return Thread.CurrentThread.ManagedThreadId
                                                      End Function)

Dim t1 As New Thread(Sub()
                         Console.WriteLine("number on t1 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t1.Start()

Dim t2 As New Thread(Sub()
                         Console.WriteLine("number on t2 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t2.Start()

Dim t3 As New Thread(Sub()
                         Console.WriteLine("number on t3 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t3.Start()

' Ensure that thread IDs are not recycled if the 
' first thread completes before the last one starts.
t1.Join()
t2.Join()
t3.Join()

' Sample Output:
'       number on t1 = 11 ThreadID = 11
'       number on t3 = 11 ThreadID = 13
'       number on t2 = 11 ThreadID = 12
'       Press any key to exit.

Si necesita datos independientes en cada subproceso, use el tipo ThreadLocal<T>, como se describe más adelante en este tema.If you require separate data on each thread, use the ThreadLocal<T> type, as described later in this topic.

Algunos constructores Lazy<T> tienen un parámetro booleano denominado isThreadSafe que se usa para especificar si se obtendrá acceso a la propiedad Value desde varios subprocesos.Some Lazy<T> constructors have a Boolean parameter named isThreadSafe that is used to specify whether the Value property will be accessed from multiple threads. Si piensa tener acceso a la propiedad desde un solo subproceso, pase false para obtener una pequeña mejora en el rendimiento.If you intend to access the property from just one thread, pass in false to obtain a modest performance benefit. Si piensa tener acceso a la propiedad desde varios subprocesos, pase true para indicarle a la instancia Lazy<T> que controle correctamente las condiciones de carrera en las que un subproceso produce una excepción durante la inicialización.If you intend to access the property from multiple threads, pass in true to instruct the Lazy<T> instance to correctly handle race conditions in which one thread throws an exception at initialization time.

Algunos constructores Lazy<T> tienen un parámetro LazyThreadSafetyMode denominado mode.Some Lazy<T> constructors have a LazyThreadSafetyMode parameter named mode. Estos constructores proporcionan un modo adicional de seguridad para subprocesos.These constructors provide an additional thread safety mode. En la tabla siguiente se muestra la manera en que la seguridad para subprocesos de un objeto Lazy<T> se ve afectada por los parámetros del constructor que especifican la seguridad para subprocesos.The following table shows how the thread safety of a Lazy<T> object is affected by constructor parameters that specify thread safety. Cada constructor tiene como máximo un parámetro de este tipo.Each constructor has at most one such parameter.

Seguridad para subprocesos del objetoThread safety of the object LazyThreadSafetyMode``modeparámetro deLazyThreadSafetyMode mode parameter Parámetro booleano isThreadSafeBoolean isThreadSafe parameter Sin parámetros de seguridad para subprocesosNo thread safety parameters
Totalmente seguro para subprocesos; solo intenta inicializar el valor un subproceso de cada vez.Fully thread-safe; only one thread at a time tries to initialize the value. ExecutionAndPublication true Sí.Yes.
No es seguro para subprocesos.Not thread-safe. None false No aplicable.Not applicable.
Totalmente seguro para subprocesos; los subprocesos se apresuran a inicializar el valor.Fully thread-safe; threads race to initialize the value. PublicationOnly No aplicable.Not applicable. No aplicable.Not applicable.

Como se muestra en la tabla, especificar LazyThreadSafetyMode.ExecutionAndPublication para el parámetro mode equivale a especificar true para el parámetro isThreadSafe, y especificar LazyThreadSafetyMode.None equivale a especificar false.As the table shows, specifying LazyThreadSafetyMode.ExecutionAndPublication for the mode parameter is the same as specifying true for the isThreadSafe parameter, and specifying LazyThreadSafetyMode.None is the same as specifying false.

Si se especifica LazyThreadSafetyMode.PublicationOnly, varios subprocesos pueden intentar inicializar la instancia Lazy<T>.Specifying LazyThreadSafetyMode.PublicationOnly allows multiple threads to attempt to initialize the Lazy<T> instance. Solo un subproceso puede ganar esta carrera, y los demás subprocesos recibirán el valor que haya inicializado el subproceso ganador.Only one thread can win this race, and all the other threads receive the value that was initialized by the successful thread. Si se produce una excepción en un subproceso durante la inicialización, dicho subproceso no recibe el valor establecido por el subproceso ganador.If an exception is thrown on a thread during initialization, that thread does not receive the value set by the successful thread. Las excepciones no se almacenan en caché, por lo que un intento posterior para tener acceso a la propiedad Value puede dar lugar a un inicialización correcta.Exceptions are not cached, so a subsequent attempt to access the Value property can result in successful initialization. Esto difiere de la manera en que se tratan las excepciones en otros modos, como se describe en la sección siguiente.This differs from the way exceptions are treated in other modes, which is described in the following section. Para obtener más información, vea la enumeración LazyThreadSafetyMode.For more information, see the LazyThreadSafetyMode enumeration.

Excepciones en objetos diferidosExceptions in Lazy Objects

Como ya se ha indicado, un objeto Lazy<T> siempre devuelve el mismo objeto o valor con el que se ha inicializado y, por tanto, la propiedad Value es de solo lectura.As stated earlier, a Lazy<T> object always returns the same object or value that it was initialized with, and therefore the Value property is read-only. Si habilita el almacenamiento en caché de excepciones, esta inmutabilidad también se aplica al comportamiento de las excepciones.If you enable exception caching, this immutability also extends to exception behavior. Si un objeto con inicialización diferida tiene habilitado el almacenamiento en caché de excepciones y produce una excepción desde su método de inicialización cuando Value se obtiene acceso por primera vez a la propiedad, se produce la misma excepción en cada intento posterior de acceder a la Value propiedad.If a lazy-initialized object has exception caching enabled and throws an exception from its initialization method when the Value property is first accessed, that same exception is thrown on every subsequent attempt to access the Value property. En otras palabras, el constructor del tipo encapsulado nunca se vuelve a invocar, ni siquiera en escenarios multiproceso.In other words, the constructor of the wrapped type is never re-invoked, even in multithreaded scenarios. Por lo tanto, el objeto Lazy<T> no puede producir una excepción en un acceso y devolver un valor en un acceso posterior.Therefore, the Lazy<T> object cannot throw an exception on one access and return a value on a subsequent access.

El almacenamiento en caché de excepciones se habilita cuando se usa cualquier constructor System.Lazy<T> que toma un método de inicialización (un parámetro valueFactory); por ejemplo, se habilita cuando se usa el constructor Lazy(T)(Func(T)).Exception caching is enabled when you use any System.Lazy<T> constructor that takes an initialization method (valueFactory parameter); for example, it is enabled when you use the Lazy(T)(Func(T))constructor. Si el constructor también toma un valor LazyThreadSafetyMode (un parámetro mode), especifique LazyThreadSafetyMode.ExecutionAndPublication o LazyThreadSafetyMode.None.If the constructor also takes a LazyThreadSafetyMode value (mode parameter), specify LazyThreadSafetyMode.ExecutionAndPublication or LazyThreadSafetyMode.None. Al especificar un método de inicialización, se permite el almacenamiento en caché de excepciones para estos dos modos.Specifying an initialization method enables exception caching for these two modes. El método de inicialización puede ser muy simple.The initialization method can be very simple. Por ejemplo, podría llamar al constructor sin parámetros para T : new Lazy<Contents>(() => new Contents(), mode) en C# o New Lazy(Of Contents)(Function() New Contents()) en Visual Basic.For example, it might call the parameterless constructor for T: new Lazy<Contents>(() => new Contents(), mode) in C#, or New Lazy(Of Contents)(Function() New Contents()) in Visual Basic. Si usa un constructor System.Lazy<T> que no especifica un método de inicialización, las excepciones que inicie el constructor sin parámetros para T no se almacenarán en caché.If you use a System.Lazy<T> constructor that does not specify an initialization method, exceptions that are thrown by the parameterless constructor for T are not cached. Para obtener más información, vea la enumeración LazyThreadSafetyMode.For more information, see the LazyThreadSafetyMode enumeration.

Nota

Si crea un objeto Lazy<T> con el parámetro de constructor isThreadSafe establecido en false o el parámetro de constructor mode establecido en LazyThreadSafetyMode.None, debe tener acceso al objeto Lazy<T> desde un subproceso o proporcionar su propia sincronización.If you create a Lazy<T> object with the isThreadSafe constructor parameter set to false or the mode constructor parameter set to LazyThreadSafetyMode.None, you must access the Lazy<T> object from a single thread or provide your own synchronization. Esto se aplica a todos los aspectos del objeto, incluido el almacenamiento en caché de excepciones.This applies to all aspects of the object, including exception caching.

Como ya se ha indicado en la sección anterior, los objetos Lazy<T> creados al especificar LazyThreadSafetyMode.PublicationOnly tratan las excepciones de forma diferente.As noted in the previous section, Lazy<T> objects created by specifying LazyThreadSafetyMode.PublicationOnly treat exceptions differently. Con PublicationOnly, varios subprocesos pueden competir para inicializar la instancia Lazy<T>.With PublicationOnly, multiple threads can compete to initialize the Lazy<T> instance. En este caso, las excepciones no se almacenan en caché y los intentos para obtener acceso a la propiedad Value pueden continuar hasta que se complete la inicialización.In this case, exceptions are not cached, and attempts to access the Value property can continue until initialization is successful.

En la tabla siguiente se resume la forma en que los constructores Lazy<T> controlan el almacenamiento en caché de excepciones.The following table summarizes the way the Lazy<T> constructors control exception caching.

ConstructorConstructor Modo de seguridad para subprocesosThread safety mode Usa método de inicializaciónUses initialization method Las excepciones se almacenan en cachéExceptions are cached
Lazy(T)()Lazy(T)() (ExecutionAndPublication)(ExecutionAndPublication) NoNo NoNo
Lazy(T)(Func(T))Lazy(T)(Func(T)) (ExecutionAndPublication)(ExecutionAndPublication) Yes Yes
Lazy(T)(Boolean)Lazy(T)(Boolean) True (ExecutionAndPublication) o false (None)True (ExecutionAndPublication) or false (None) NoNo NoNo
Lazy(T)(Func(T), Boolean)Lazy(T)(Func(T), Boolean) True (ExecutionAndPublication) o false (None)True (ExecutionAndPublication) or false (None) Yes Yes
Lazy(T)(LazyThreadSafetyMode)Lazy(T)(LazyThreadSafetyMode) Especificado por el usuarioUser-specified NoNo NoNo
Lazy(T)(Func(T), LazyThreadSafetyMode)Lazy(T)(Func(T), LazyThreadSafetyMode) Especificado por el usuarioUser-specified Yes No si el usuario especifica PublicationOnly; en caso contrario, sí.No if user specifies PublicationOnly; otherwise, yes.

Implementar una propiedad con inicialización diferidaImplementing a Lazy-Initialized Property

Para implementar una propiedad pública mediante la inicialización diferida, defina el campo de respaldo de la propiedad como Lazy<T> y devuelva la propiedad Value desde el descriptor de acceso get de la propiedad.To implement a public property by using lazy initialization, define the backing field of the property as a Lazy<T>, and return the Value property from the get accessor of the property.

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 additional
            // initialization steps here.
            return new Orders(this.CustomerID);
        });
    }

    public Orders MyOrders
    {
        get
        {
            // Orders is created on first access here.
            return _orders.Value;
        }
    }
}
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

La propiedad Value es de solo lectura; por lo tanto, la propiedad que la expone no tiene ningún descriptor de acceso set.The Value property is read-only; therefore, the property that exposes it has no set accessor. Si necesita una propiedad de lectura/escritura respaldada por un objeto Lazy<T>, el descriptor de acceso set debe crear un objeto Lazy<T> y asignarlo a la memoria auxiliar.If you require a read/write property backed by a Lazy<T> object, the set accessor must create a new Lazy<T> object and assign it to the backing store. El descriptor de acceso set debe crear una expresión lambda que devuelva el nuevo valor de propiedad que se ha pasado al descriptor de acceso set y pasar dicha expresión lambda al constructor para el nuevo objeto Lazy<T>.The set accessor must create a lambda expression that returns the new property value that was passed to the set accessor, and pass that lambda expression to the constructor for the new Lazy<T> object. El siguiente acceso de la propiedad Value provocará la inicialización del nuevo objeto Lazy<T>, y su propiedad Value devolverá a partir de entonces el nuevo valor que se ha asignado a la propiedad.The next access of the Value property will cause initialization of the new Lazy<T>, and its Value property will thereafter return the new value that was assigned to the property. El objetivo de este complicado proceso consiste en conservar las protecciones multiproceso integradas en Lazy<T>.The reason for this convoluted arrangement is to preserve the multithreading protections built into Lazy<T>. De lo contrario, los descriptores de acceso de propiedad tendrían que almacenar en caché el primer valor devuelto por la propiedad Value y modificar solo el valor almacenado en caché, y usted tendría que escribir su propio código seguro para subprocesos para hacerlo.Otherwise, the property accessors would have to cache the first value returned by the Value property and only modify the cached value, and you would have to write your own thread-safe code to do that. Debido a las inicializaciones adicionales que requiere una propiedad de lectura/escritura respaldada por un objeto Lazy<T>, el rendimiento podría no ser aceptable.Because of the additional initializations required by a read/write property backed by a Lazy<T> object, the performance might not be acceptable. Además, en función del escenario, podría ser necesaria una coordinación adicional para evitar condiciones de carrera entre los establecedores y los captadores.Furthermore, depending on the specific scenario, additional coordination might be required to avoid race conditions between setters and getters.

Inicialización diferida local de subprocesosThread-Local Lazy Initialization

En algunos escenarios multiproceso, podría interesarle asignarle a cada subproceso sus propios datos privados.In some multithreaded scenarios, you might want to give each thread its own private data. Estos datos se denominan datos locales de subproceso.Such data is called thread-local data. En .NET Framework versión 3.5 y anteriores, se podía aplicar el atributo ThreadStatic a una variable estática para convertirla en una variable local de subproceso.In the .NET Framework version 3.5 and earlier, you could apply the ThreadStatic attribute to a static variable to make it thread-local. Pero el uso del atributo ThreadStatic puede producir pequeños errores.However, using the ThreadStatic attribute can lead to subtle errors. Por ejemplo, incluso las instrucciones de inicialización básicas harán que la variable solo se inicialice en el primer subproceso que tenga acceso a ella, tal como se muestra en el ejemplo siguiente.For example, even basic initialization statements will cause the variable to be initialized only on the first thread that accesses it, as shown in the following example.

[ThreadStatic]
static int counter = 1;
<ThreadStatic()>
Shared counter As Integer

En todos los demás subprocesos, la variable se inicializará mediante su valor predeterminado (cero).On all other threads, the variable will be initialized by using its default value (zero). Como alternativa en .NET Framework versión 4, puede usar el tipo System.Threading.ThreadLocal<T> para crear una variable local de subproceso basada en instancias que se inicialice en todos los subprocesos mediante el delegado Action<T> que proporcione.As an alternative in the .NET Framework version 4, you can use the System.Threading.ThreadLocal<T> type to create an instance-based, thread-local variable that is initialized on all threads by the Action<T> delegate that you provide. En el ejemplo siguiente, todos los subprocesos que tienen acceso a counter tendrán 1 como valor inicial.In the following example, all threads that access counter will see its starting value as 1.

ThreadLocal<int> betterCounter = new ThreadLocal<int>(() => 1);
Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)

ThreadLocal<T> encapsula el objeto de la misma manera que Lazy<T>, pero con estas diferencias básicas:ThreadLocal<T> wraps its object in much the same way as Lazy<T>, with these essential differences:

  • Cada subproceso inicializa la variable local de subproceso mediante sus propios datos privados, que no son accesibles desde otros subprocesos.Each thread initializes the thread-local variable by using its own private data that is not accessible from other threads.

  • La propiedad ThreadLocal<T>.Value es de lectura y escritura y se puede modificar todas las veces que se quiera.The ThreadLocal<T>.Value property is read-write, and can be modified any number of times. Esto puede afectar a la propagación de excepciones; por ejemplo, una operación get puede producir una excepción, pero la siguiente puede inicializar correctamente el valor.This can affect exception propagation, for example, one get operation can raise an exception but the next one can successfully initialize the value.

  • Si no se proporciona ningún delegado de inicialización, ThreadLocal<T> inicializará su tipo encapsulado mediante el valor predeterminado del tipo.If no initialization delegate is provided, ThreadLocal<T> will initialize its wrapped type by using the default value of the type. En este sentido, ThreadLocal<T> es coherente con el atributo ThreadStaticAttribute.In this regard, ThreadLocal<T> is consistent with the ThreadStaticAttribute attribute.

En el ejemplo siguiente se muestra que cada subproceso que tiene acceso a la instancia ThreadLocal<int> obtiene su propia copia única de los datos.The following example demonstrates that every thread that accesses the ThreadLocal<int> instance gets its own unique copy of the data.

// 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
*/
' 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 

Variables locales de subproceso en Parallel.For y ForEachThread-Local Variables in Parallel.For and ForEach

Cuando se usa el método Parallel.For o Parallel.ForEach para iterar en los orígenes de datos en paralelo, puede usar las sobrecargas que tienen compatibilidad integrada con datos locales de subproceso.When you use the Parallel.For method or Parallel.ForEach method to iterate over data sources in parallel, you can use the overloads that have built-in support for thread-local data. En estos métodos, la localidad del subproceso se logra mediante el uso de delegados locales para crear los datos, obtener acceso a ellos y limpiarlos.In these methods, the thread-locality is achieved by using local delegates to create, access, and clean up the data. Para obtener más información, vea Cómo: Escribir un bucle Parallel.For con variables locales de subproceso y How to: Write a Parallel.ForEach Loop with Thread-Local Variables (Cómo: Escribir un bucle Parallel.ForEach con variables locales de partición).For more information, see How to: Write a Parallel.For Loop with Thread-Local Variables and How to: Write a Parallel.ForEach Loop with Partition-Local Variables.

Usar la inicialización diferida para escenarios con poca sobrecargaUsing Lazy Initialization for Low-Overhead Scenarios

En los escenarios en los que tiene que inicializar de forma diferida un gran número de objetos, podría decidir que el proceso de encapsular cada objeto en un objeto Lazy<T> requiere demasiada memoria o demasiados recursos informáticos.In scenarios where you have to lazy-initialize a large number of objects, you might decide that wrapping each object in a Lazy<T> requires too much memory or too many computing resources. O bien, es posible que tenga requisitos estrictos sobre cómo se expone la inicialización diferida.Or, you might have stringent requirements about how lazy initialization is exposed. En tales casos, puede usar los métodos static (Shared en Visual Basic) de la clase System.Threading.LazyInitializer para inicializar de forma diferida cada objeto sin encapsularlo en una instancia de Lazy<T>.In such cases, you can use the static (Shared in Visual Basic) methods of the System.Threading.LazyInitializer class to lazy-initialize each object without wrapping it in an instance of Lazy<T>.

En el ejemplo siguiente se da por supuesto que, en lugar de encapsular todo un objeto Orders en un objeto Lazy<T>, solo se inicializan de forma diferida los objetos Order individuales si son necesarios.In the following example, assume that, instead of wrapping an entire Orders object in one Lazy<T> object, you have lazy-initialized individual Order objects only if they are required.

// 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);
            });
    }
}
' 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

En este ejemplo, observe que el procedimiento de inicialización se invoca en cada iteración del bucle.In this example, notice that the initialization procedure is invoked on every iteration of the loop. En escenarios multiproceso, el primer subproceso que invoca el procedimiento de inicialización es aquel cuyo valor ven todos los subprocesos.In multi-threaded scenarios, the first thread to invoke the initialization procedure is the one whose value is seen by all threads. Los subprocesos posteriores también invocan el procedimiento de inicialización, pero sus resultados no se usan.Later threads also invoke the initialization procedure, but their results are not used. Si este tipo de condición de carrera potencial no es aceptable, use la sobrecarga de LazyInitializer.EnsureInitialized que toma un argumento booleano y un objeto de sincronización.If this kind of potential race condition is not acceptable, use the overload of LazyInitializer.EnsureInitialized that takes a Boolean argument and a synchronization object.

Consulte tambiénSee also