Initialisation tardiveLazy Initialization

L’initialisation tardive d’un objet signifie que sa création est différée jusqu’à sa première utilisation.Lazy initialization of an object means that its creation is deferred until it is first used. (Pour cette rubrique, les termes initialisation tardive et instanciation tardive sont synonymes.) L’initialisation tardive est principalement utilisée pour améliorer les performances, éviter les calculs inutiles et réduire les besoins en mémoire programme.(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. Voici les scénarios les plus courants :These are the most common scenarios:

  • Lorsqu’un objet est coûteux à créer, et qu’il est possible que le programme ne l’utilise pas.When you have an object that is expensive to create, and the program might not use it. Par exemple, supposons que vous ayez en mémoire un objet Customer avec une propriété Orders contenant un grand tableau d’objets Order qui, pour être initialisé, nécessite une connexion de base de données.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 l’utilisateur ne demande jamais à afficher les commandes ou à utiliser les données dans un calcul, il est inutile d’utiliser la mémoire système ou les cycles de calcul pour les créer.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. En utilisant Lazy<Orders> pour déclarer l’objet Orders en vue de son initialisation tardive, vous évitez de gaspiller les ressources système lorsque l’objet n’est pas utilisé.By using Lazy<Orders> to declare the Orders object for lazy initialization, you can avoid wasting system resources when the object is not used.

  • Si vous avez un objet qui est coûteux à créer, et si vous souhaitez différer sa création jusqu’à ce que d’autres opérations coûteuses soient terminées.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. Par exemple, supposons que votre programme charge plusieurs instances d’objet lorsqu’il démarre, mais que seules certaines d’entre elles soient immédiatement nécessaires.For example, assume that your program loads several object instances when it starts, but only some of them are required immediately. Vous pouvez améliorer les performances de démarrage du programme en différant l’initialisation des objets qui ne sont pas nécessaires tant que les objets nécessaires n’ont pas été créés.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.

Même si vous pouvez écrire votre propre code pour effectuer une initialisation tardive, nous vous recommandons d’utiliser Lazy<T>.Although you can write your own code to perform lazy initialization, we recommend that you use Lazy<T> instead. Lazy<T> et ses types associés prennent également en charge la cohérence de thread et fournissent une stratégie cohérente de propagation des exceptions.Lazy<T> and its related types also support thread-safety and provide a consistent exception propagation policy.

Le tableau suivant répertorie les types fournis par le .NET Framework version 4 pour permettre l’initialisation tardive dans différents scénarios.The following table lists the types that the .NET Framework version 4 provides to enable lazy initialization in different scenarios.

TypeType DescriptionDescription
Lazy<T> Classe wrapper qui fournit une sémantique d’initialisation tardive pour toute bibliothèque de classes ou type défini par l’utilisateur.A wrapper class that provides lazy initialization semantics for any class library or user-defined type.
ThreadLocal<T> Similaire à Lazy<T>, sauf qu’il fournit une sémantique d’initialisation tardive en fonction du thread local.Resembles Lazy<T> except that it provides lazy initialization semantics on a thread-local basis. Chaque thread a accès à sa propre valeur.Every thread has access to its own unique value.
LazyInitializer Fournit des méthodes static avancées (Shared en Visual Basic) pour l’initialisation tardive des objets, sans la surcharge d’une classe.Provides advanced static (Shared in Visual Basic) methods for lazy initialization of objects without the overhead of a class.

Initialisation tardive de baseBasic Lazy Initialization

Pour définir un type initialisé tardivement (par exemple, MyType), utilisez Lazy<MyType> (Lazy(Of MyType) en Visual Basic), comme illustré dans l’exemple suivant.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 aucun délégué n’est passé dans le constructeur Lazy<T>, le type encapsulé est créé à l’aide de Activator.CreateInstance lors du premier accès à la propriété de la valeur.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 le type n’a pas de constructeur sans paramètre, une exception Runtime est levée.If the type does not have a parameterless constructor, a run-time exception is thrown.

Dans l’exemple suivant, supposons que Orders soit une classe qui contienne un tableau d’objets Order récupérés à partir d’une base de données.In the following example, assume that Orders is a class that contains an array of Order objects retrieved from a database. Un objet Customer contient une instance de Orders, mais en fonction des actions de l’utilisateur, les données de l’objet Orders peuvent ne pas être nécessaires.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)()

Vous pouvez également passer un délégué dans le constructeur Lazy<T> qui appelle une surcharge de constructeur sur le type encapsulé au moment de la création, et effectuer d’autres étapes d’initialisation nécessaires, comme indiqué dans l’exemple suivant.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))

Une fois que l’objet différé est créé, aucune instance de Orders n’est créée tant que la propriété Value de la variable tardive n’a pas fait l’objet d’un accès.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. Lors du premier accès à la propriété, le type encapsulé est créé, retourné, puis stocké en vue d’une utilisation ultérieure.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 objet Lazy<T> retourne toujours le même objet (ou la même valeur) qui a été utilisé lors de son initialisation.A Lazy<T> object always returns the same object or value that it was initialized with. Par conséquent, la propriété Value est en lecture seule.Therefore, the Value property is read-only. Si Value stocke un type référence, vous ne pouvez pas lui attribuer un nouvel objetIf Value stores a reference type, you cannot assign a new object to it. (toutefois, vous pouvez modifier la valeur de ses champs et de ses propriétés publics paramétrables). Si Value stocke un type valeur, vous ne pouvez pas modifier sa valeur.(However, you can change the value of its settable public fields and properties.) If Value stores a value type, you cannot modify its value. Toutefois, vous pouvez créer une variable en rappelant le constructeur de variable à l’aide de nouveaux arguments.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 nouvelle instance tardive, comme la précédente, n’instancie pas Orders tant que sa propriété Value n’a pas fait l’objet d’un accès.The new lazy instance, like the earlier one, does not instantiate Orders until its Value property is first accessed.

Initialisation thread-safeThread-Safe Initialization

Par défaut, les objets Lazy<T> sont thread-safe.By default, Lazy<T> objects are thread-safe. Autrement dit, si le constructeur ne spécifie pas le type de cohérence de thread, les objets Lazy<T> qu’il crée sont thread-safe.That is, if the constructor does not specify the kind of thread safety, the Lazy<T> objects it creates are thread-safe. Dans les scénarios multithreads, le premier thread qui accède à la propriété Value d’un objet Lazy<T> thread-safe initialise celui-ci pour tous les accès suivants sur tous les threads. De plus, tous les threads partagent les mêmes données.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. Par conséquent, le thread qui initialise l’objet importe peu, et les conditions de concurrence sont sans conséquences.Therefore, it does not matter which thread initializes the object, and race conditions are benign.

Notes

Vous pouvez étendre cette cohérence aux conditions d’erreur à l’aide de la mise en cache des exceptions.You can extend this consistency to error conditions by using exception caching. Pour plus d’informations, consultez la section suivante, Exceptions des objets différés.For more information, see the next section, Exceptions in Lazy Objects.

L’exemple suivant montre que la même instance Lazy<int> a la même valeur pour trois threads différents.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 chaque thread doit contenir des données distinctes, utilisez le type ThreadLocal<T>, comme décrit plus loin dans cette rubrique.If you require separate data on each thread, use the ThreadLocal<T> type, as described later in this topic.

Certains constructeurs Lazy<T> ont un paramètre booléen nommé isThreadSafe qui permet de spécifier si la propriété Value est accessible à partir de plusieurs threads.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 vous envisagez d’accéder à la propriété à partir d’un seul thread, vous pouvez passer false pour obtenir un gain de performances modeste.If you intend to access the property from just one thread, pass in false to obtain a modest performance benefit. Si vous avez l’intention d’accéder à la propriété à partir de plusieurs threads, passez true pour indiquer à l’instance Lazy<T> qu’elle doit gérer correctement les conditions de concurrence dans lesquelles un thread lève une exception au moment de l’initialisation.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.

Certains constructeurs Lazy<T> ont un paramètre LazyThreadSafetyMode nommé mode.Some Lazy<T> constructors have a LazyThreadSafetyMode parameter named mode. Ces constructeurs fournissent un mode de cohérence de thread supplémentaire.These constructors provide an additional thread safety mode. Le tableau suivant montre comment la cohérence de thread d’un objet Lazy<T> est affectée par les paramètres du constructeur qui spécifient la cohérence de thread.The following table shows how the thread safety of a Lazy<T> object is affected by constructor parameters that specify thread safety. Chaque constructeur comprend un tel paramètre.Each constructor has at most one such parameter.

Cohérence de thread de l’objetThread safety of the object Paramètre LazyThreadSafetyMode modeLazyThreadSafetyMode mode parameter Paramètre isThreadSafe booléenBoolean isThreadSafe parameter Aucun paramètre de cohérence de threadNo thread safety parameters
Entièrement thread-safe. Seul un thread à la fois tente d’initialiser la valeur.Fully thread-safe; only one thread at a time tries to initialize the value. ExecutionAndPublication true Oui.Yes.
Non thread-safe.Not thread-safe. None false Non applicable.Not applicable.
Entièrement thread-safe. Concurrence de threads pour initialiser la valeur.Fully thread-safe; threads race to initialize the value. PublicationOnly Non applicable.Not applicable. Non applicable.Not applicable.

Comme le montre le tableau, spécifier LazyThreadSafetyMode.ExecutionAndPublication pour le paramètre mode équivaut à spécifier true pour le paramètre isThreadSafe, et spécifier LazyThreadSafetyMode.None revient à spécifier 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.

Le fait de spécifier LazyThreadSafetyMode.PublicationOnly permet à plusieurs threads de tenter d’initialiser l’instance Lazy<T>.Specifying LazyThreadSafetyMode.PublicationOnly allows multiple threads to attempt to initialize the Lazy<T> instance. Seul un thread peut gagner cette course. Tous les autres threads reçoivent la valeur qui a été initialisée par le thread gagnant.Only one thread can win this race, and all the other threads receive the value that was initialized by the successful thread. Si une exception est levée sur un thread pendant l’initialisation, ce thread ne reçoit pas la valeur définie par le thread gagnant.If an exception is thrown on a thread during initialization, that thread does not receive the value set by the successful thread. Les exceptions ne sont pas mises en cache. De fait, une nouvelle tentative d’accès à la propriété Value peut aboutir à une initialisation.Exceptions are not cached, so a subsequent attempt to access the Value property can result in successful initialization. Ce traitement des exceptions est différent de celui des autres modes, et fait l’objet de la section suivante.This differs from the way exceptions are treated in other modes, which is described in the following section. Pour plus d’informations, consultez l’énumération LazyThreadSafetyMode.For more information, see the LazyThreadSafetyMode enumeration.

Exceptions des objets différésExceptions in Lazy Objects

Comme indiqué précédemment, un objet Lazy<T> retourne toujours le même objet (ou la même valeur) avec lequel il a été initialisé. De fait, la propriété Value est en lecture seule.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 vous activez la mise en cache des exceptions, cette immuabilité s’étend également au comportement des exceptions.If you enable exception caching, this immutability also extends to exception behavior. Si la mise en cache des exceptions est activée pour un objet initialisé tardive et qu’une exception est levée à partir de Value sa méthode d’initialisation lorsque la propriété est accédée pour la première fois, cette Value même exception est levée lors de chaque nouvelle tentative d’accès à la propriété. .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 d’autres termes, le constructeur du type encapsulé n’est jamais rappelé, même dans les scénarios multithreads.In other words, the constructor of the wrapped type is never re-invoked, even in multithreaded scenarios. Par conséquent, l’objet Lazy<T> ne peut pas lever une exception lors d’un accès et retourner une valeur lors d’un accès ultérieur.Therefore, the Lazy<T> object cannot throw an exception on one access and return a value on a subsequent access.

La mise en cache des exceptions est activée lorsque vous utilisez un constructeur System.Lazy<T> qui accepte une méthode d’initialisation (un paramètre valueFactory). Par exemple, elle est activée lorsque vous utilisez le constructeur 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 le constructeur accepte également une valeur LazyThreadSafetyMode (un paramètre mode), spécifiez LazyThreadSafetyMode.ExecutionAndPublication ou LazyThreadSafetyMode.None.If the constructor also takes a LazyThreadSafetyMode value (mode parameter), specify LazyThreadSafetyMode.ExecutionAndPublication or LazyThreadSafetyMode.None. La spécification d’une méthode d’initialisation permet la mise en cache des exceptions pour ces deux modes.Specifying an initialization method enables exception caching for these two modes. La méthode d’initialisation peut être très simple.The initialization method can be very simple. Par exemple, il peut appeler le constructeur sans paramètre pour T: new Lazy<Contents>(() => new Contents(), mode) dans C#, ou New Lazy(Of Contents)(Function() New Contents()) dans 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 vous utilisez un constructeur System.Lazy<T> qui ne spécifie pas de méthode d’initialisation, les exceptions levées par le constructeur sans paramètre pour T ne sont pas mises en cache.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. Pour plus d’informations, consultez l’énumération LazyThreadSafetyMode.For more information, see the LazyThreadSafetyMode enumeration.

Notes

Si vous créez un objet Lazy<T> avec le paramètre de constructeur isThreadSafe défini sur false ou le paramètre de constructeur mode défini sur LazyThreadSafetyMode.None, vous devez accéder à l’objet Lazy<T> à partir d’un thread unique ou fournir votre propre synchronisation.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. Cela s’applique à tous les aspects de l’objet, y compris la mise en cache des exceptions.This applies to all aspects of the object, including exception caching.

Comme indiqué dans la section précédente, les objets Lazy<T> créés en spécifiant LazyThreadSafetyMode.PublicationOnly traitent les exceptions différemment.As noted in the previous section, Lazy<T> objects created by specifying LazyThreadSafetyMode.PublicationOnly treat exceptions differently. Avec PublicationOnly, plusieurs threads peuvent rivaliser pour initialiser l’instance Lazy<T>.With PublicationOnly, multiple threads can compete to initialize the Lazy<T> instance. Dans ce cas, les exceptions ne sont pas mises en cache, et les tentatives d’accès à la propriété Value peuvent continuer jusqu’à ce que l’initialisation soit effectuée.In this case, exceptions are not cached, and attempts to access the Value property can continue until initialization is successful.

Le tableau suivant récapitule la façon dont les constructeurs Lazy<T> contrôlent la mise en cache des exceptions.The following table summarizes the way the Lazy<T> constructors control exception caching.

ConstructeurConstructor Mode de cohérence de threadThread safety mode Utilise la méthode d’initialisationUses initialization method Exceptions mises en cacheExceptions are cached
Lazy(T)()Lazy(T)() (ExecutionAndPublication)(ExecutionAndPublication) NonNo NonNo
Lazy(T)(Func(T))Lazy(T)(Func(T)) (ExecutionAndPublication)(ExecutionAndPublication) OuiYes OuiYes
Lazy(T)(Boolean)Lazy(T)(Boolean) True (ExecutionAndPublication) ou false (None)True (ExecutionAndPublication) or false (None) NonNo NonNo
Lazy(T)(Func(T), Boolean)Lazy(T)(Func(T), Boolean) True (ExecutionAndPublication) ou false (None)True (ExecutionAndPublication) or false (None) OuiYes OuiYes
Lazy(T)(LazyThreadSafetyMode)Lazy(T)(LazyThreadSafetyMode) Spécifié par l’utilisateurUser-specified NonNo NonNo
Lazy(T)(Func(T), LazyThreadSafetyMode)Lazy(T)(Func(T), LazyThreadSafetyMode) Spécifié par l’utilisateurUser-specified OuiYes Non, si l’utilisateur spécifie PublicationOnly ; sinon, Oui.No if user specifies PublicationOnly; otherwise, yes.

Implémentation d’une propriété à initialisation tardiveImplementing a Lazy-Initialized Property

Pour implémenter une propriété publique à l’aide de l’initialisation tardive, définissez le champ de stockage de la propriété comme un Lazy<T>, puis retournez la propriété Value à partir de l’accesseur get de la propriété.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 additonal 
            // 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 propriété Value est en lecture seule, par conséquent, la propriété qui l’expose n’a pas d’accesseur set.The Value property is read-only; therefore, the property that exposes it has no set accessor. Si vous avez besoin d’une propriété en lecture/écriture stockée par un objet Lazy<T>, l’accesseur set doit créer un nouvel objet Lazy<T> et l’assigner au magasin de stockage.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. L’accesseur set doit créer une expression lambda qui retourne la nouvelle valeur de propriété qui a été passée à l’accesseur set, et passer cette expression lambda au constructeur pour le nouvel objet 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. Le prochain accès à la propriété Value va entraîner l’initialisation du nouveau Lazy<T>, et sa propriété Value va alors retourner la nouvelle valeur qui a été affectée à la propriété.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. L’objectif de cet arrangement complexe est de conserver les protections de multithreading intégrées à Lazy<T>.The reason for this convoluted arrangement is to preserve the multithreading protections built into Lazy<T>. Sans cela, les accesseurs de propriété devraient mettre en cache la première valeur retournée par la propriété Value et modifier uniquement la valeur mise en cache. En plus, vous auriez à écrire votre propre code thread-safe pour réaliser cela.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. En raison des initialisations supplémentaires exigées par une propriété en lecture/écriture stockée dans un objet Lazy<T>, les performances peuvent ne pas être acceptables.Because of the additional initializations required by a read/write property backed by a Lazy<T> object, the performance might not be acceptable. De plus, selon le scénario, une coordination supplémentaire peut être nécessaire pour éviter des conditions de concurrence entre les méthodes setter et getter.Furthermore, depending on the specific scenario, additional coordination might be required to avoid race conditions between setters and getters.

Initialisation tardive de thread localThread-Local Lazy Initialization

Dans certains scénarios multithreads, vous pouvez souhaiter que chaque thread ait ses propres données privées.In some multithreaded scenarios, you might want to give each thread its own private data. Ces données sont appelées données de thread local.Such data is called thread-local data. Dans le .NET Framework 3.5 et versions antérieures, vous pouvez appliquer l’attribut ThreadStatic à une variable statique pour qu’elle devienne une variable de thread local.In the .NET Framework version 3.5 and earlier, you could apply the ThreadStatic attribute to a static variable to make it thread-local. Toutefois, l’utilisation de l’attribut ThreadStatic peut entraîner des erreurs subtiles.However, using the ThreadStatic attribute can lead to subtle errors. Par exemple, même avec des instructions d’initialisation basiques, la variable est initialisée uniquement sur le premier thread qui y accède, comme indiqué dans l’exemple suivant.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

Sur tous les autres threads, la variable est initialisée à l’aide de sa valeur par défaut (zéro).On all other threads, the variable will be initialized by using its default value (zero). En guise d’alternative dans le .NET Framework version 4, vous pouvez utiliser le type System.Threading.ThreadLocal<T> pour créer une variable de thread local basée sur l’instance, qui est initialisée sur tous les threads par le délégué Action<T> que vous fournissez.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. Dans l’exemple suivant, tous les threads qui accèdent à counter vont voir que sa valeur de départ est égale à 1.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> encapsule son objet de la même façon que Lazy<T>, avec toutefois ces différences essentielles :ThreadLocal<T> wraps its object in much the same way as Lazy<T>, with these essential differences:

  • Chaque thread initialise la variable de thread local à l’aide de ses données privées, qui ne sont pas accessibles par d’autres threads.Each thread initializes the thread-local variable by using its own private data that is not accessible from other threads.

  • La propriété ThreadLocal<T>.Value est en lecture-écriture et peut être modifiée autant de fois que nécessaire.The ThreadLocal<T>.Value property is read-write, and can be modified any number of times. Cela peut affecter la propagation des exceptions. Par exemple, une opération get peut lever une exception, mais celle qui suit peut initialiser la valeur.This can affect exception propagation, for example, one get operation can raise an exception but the next one can successfully initialize the value.

  • Si aucun délégué d’initialisation n’est fourni, ThreadLocal<T> va initialiser son type encapsulé à l’aide de la valeur par défaut du type.If no initialization delegate is provided, ThreadLocal<T> will initialize its wrapped type by using the default value of the type. À cet égard, ThreadLocal<T> est cohérent avec l’attribut ThreadStaticAttribute.In this regard, ThreadLocal<T> is consistent with the ThreadStaticAttribute attribute.

L’exemple suivant montre que chaque thread qui accède à l’instance ThreadLocal<int> obtient sa propre copie des données.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 de thread local dans Parallel.For et ForEachThread-Local Variables in Parallel.For and ForEach

Lorsque vous utilisez la méthode Parallel.For ou Parallel.ForEach pour parcourir des sources de données en parallèle, vous pouvez utiliser les surcharges qui ont une prise en charge intégrée pour les données de thread local.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. Dans ces méthodes, pour obtenir des données de thread local, vous devez utiliser des délégués locaux pour créer ces données, y accéder et les nettoyer.In these methods, the thread-locality is achieved by using local delegates to create, access, and clean up the data. Pour plus d’informations, consultez Guide pratique pour écrire une boucle Parallel.For avec des variables locales de thread et Guide pratique pour écrire une boucle Parallel.ForEach avec des variables locales de partition.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.

Utilisation de l’initialisation tardive pour les scénarios de faible chargeUsing Lazy Initialization for Low-Overhead Scenarios

Dans les scénarios où vous devez initialiser tardivement un grand nombre d’objets, vous pouvez décider que l’encapsulation de chaque objet dans un Lazy<T> nécessite trop de mémoire ou trop de ressources informatiques.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. Vous pouvez aussi avoir des exigences strictes sur la façon dont l’initialisation tardive est exposée.Or, you might have stringent requirements about how lazy initialization is exposed. Dans ce cas, vous pouvez utiliser les méthodes static (Shared en Visual Basic) de la classe System.Threading.LazyInitializer pour initialiser tardivement chaque objet sans l’encapsuler dans une instance 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>.

Dans l’exemple suivant, supposons que, au lieu d’encapsuler un objet Orders entier dans un objet Lazy<T>, vous n’avez que des objets Order initialisés tardivement seulement lorsqu’ils sont nécessaires.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

Dans cet exemple, notez que la procédure d’initialisation est appelée sur chaque itération de la boucle.In this example, notice that the initialization procedure is invoked on every iteration of the loop. Dans les scénarios multithreads, le premier thread à appeler la procédure d’initialisation est celui dont la valeur est visible par tous les threads.In multi-threaded scenarios, the first thread to invoke the initialization procedure is the one whose value is seen by all threads. Les threads suivants appellent également la procédure d’initialisation, mais leurs résultats ne sont pas utilisés.Later threads also invoke the initialization procedure, but their results are not used. Si ce type de condition de concurrence potentielle n’est pas acceptable, utilisez la surcharge de LazyInitializer.EnsureInitialized qui accepte un argument booléen et un objet de synchronisation.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.

Voir aussiSee also