延遲初始設定Lazy Initialization

物件的「延遲初始設定」** 表示物件一直延遲到第一次使用才建立。Lazy initialization of an object means that its creation is deferred until it is first used. (對於本主題,術語 "延遲初始化和**延遲具現化"是同義字。延遲初始化主要用於提高性能、避免浪費計算和減少程式記憶體需求。(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. 以下為最常見的案例:These are the most common scenarios:

  • 當建立某個物件會耗費大量資源,而程式可能不使用它時。When you have an object that is expensive to create, and the program might not use it. 例如,假設您在記憶體中有 Customer 物件,它的 Orders 屬性包含大型的 Order 物件陣列,其初始化需要連接資料庫。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. 如果使用者從未要求顯示訂單,或使用資料進行計算,就沒必要使用系統記憶體或計算週期來建立它。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. 使用 Lazy<Orders> 宣告延遲初始設定 Orders 物件,可在不使用物件時,避免浪費系統資源。By using Lazy<Orders> to declare the Orders object for lazy initialization, you can avoid wasting system resources when the object is not used.

  • 當建立某個物件會耗費大量資源,而您想要延遲到完成其他耗費資源的作業後,再建立它。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. 例如,假設當程式啟動時會載入數個物件執行個體,但只有部分是立即需要的。For example, assume that your program loads several object instances when it starts, but only some of them are required immediately. 您可以延遲到必要的物件皆已建立後,再對不需要的物件進行初始設定,以改善程式的啟動效能。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.

雖然您可以自行撰寫程式碼執行延遲初始設定,但仍建議您改用 Lazy<T>Although you can write your own code to perform lazy initialization, we recommend that you use Lazy<T> instead. Lazy<T> 及其相關類型也支援安全執行緒,並提供一致的例外狀況傳播原則。Lazy<T> and its related types also support thread-safety and provide a consistent exception propagation policy.

下表列出 .NET Framework 第 4 版提供的類型,以在不同案例中啟用延遲初始化。The following table lists the types that the .NET Framework version 4 provides to enable lazy initialization in different scenarios.

類型Type 描述Description
Lazy<T> 為任何類別庫或使用者定義類型提供延遲初始化語意的包裝函式類別。A wrapper class that provides lazy initialization semantics for any class library or user-defined type.
ThreadLocal<T> 類似於 Lazy<T>,不同之處在於它是在執行緒區域上提供延遲初始化語意。Resembles Lazy<T> except that it provides lazy initialization semantics on a thread-local basis. 每個執行緒都可以存取自己的唯一值。Every thread has access to its own unique value.
LazyInitializer 為物件的延遲初始設定提供進階 static (Visual Basic 為 Shared) 方法,沒有類別的額外負荷。Provides advanced static (Shared in Visual Basic) methods for lazy initialization of objects without the overhead of a class.

基本延遲初始設定Basic Lazy Initialization

若要定義延遲初始化類型,例如 MyType,請使用 Lazy<MyType> (Visual Basic 為 Lazy(Of MyType)),如下列範例所示。To define a lazy-initialized type, for example, MyType, use Lazy<MyType> (Lazy(Of MyType) in Visual Basic), as shown in the following example. 如果沒有委派傳入 Lazy<T> 建構函式,第一次存取 Value 屬性時,會使用 Activator.CreateInstance 建立包裝類型。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. 如果類型沒有無參數建構函式,則引發運行時異常。If the type does not have a parameterless constructor, a run-time exception is thrown.

在下例中,假設 Orders 是類別,包含從資料庫擷取的 Order 物件陣列。In the following example, assume that Orders is a class that contains an array of Order objects retrieved from a database. Customer 物件包含 Orders 的執行個體,但視使用者的動作而 定,可能不需要來自 Orders 物件的資料。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)()

您也可以在 Lazy<T> 建構函式中傳遞委派,此建構函式會在特定時間於包裝類型上叫用特定的建構函式多載,執行任何其他需要的初始設定步驟,如下列範例中所示。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))

建立延遲物件後,要到第一次存取延遲變數的 Value 屬性後,才會建立 Orders 的執行個體。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. 第一次存取時,會建立並傳回包裝類型,儲存以供未來存取。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

Lazy<T> 物件一律傳回相同的物件或隨之初始化的值。A Lazy<T> object always returns the same object or value that it was initialized with. 因此,Value 屬性是唯讀的。Therefore, the Value property is read-only. 如果 Value 儲存參考型別,您就無法指派新的物件給它。If Value stores a reference type, you cannot assign a new object to it. (但是,您可以更改其可設置的公共欄位和屬性的值。如果Value存儲數值型別,則無法修改其值。(However, you can change the value of its settable public fields and properties.) If Value stores a value type, you cannot modify its value. 不過,您可以使用新的引數再次叫用變數的建構函式,建立新的變數。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))

新的延遲執行個體,像舊版一樣,一直要到第一次存取其 Value 屬性後,才會具現化 OrdersThe new lazy instance, like the earlier one, does not instantiate Orders until its Value property is first accessed.

初始化安全執行緒Thread-Safe Initialization

根據預設,Lazy<T> 物件是安全執行緒。By default, Lazy<T> objects are thread-safe. 也就是說,如果建構函式不指定執行緒安全的種類,它建立的 Lazy<T> 物件就會是安全執行緒。That is, if the constructor does not specify the kind of thread safety, the Lazy<T> objects it creates are thread-safe. 在多執行緒案例中,第一個存取安全執行緒 Lazy<T> 物件的 Value 屬性的執行緒,會將它初始化以在所有的執行緒上進行所有的後續存取,且所有執行緒都共用相同的資料。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. 因此,哪個執行緒初始化物件不重要,只要是良性的競爭條件即可。Therefore, it does not matter which thread initializes the object, and race conditions are benign.

注意

您可以使用快取例外狀況,擴充錯誤條件的一致性。You can extend this consistency to error conditions by using exception caching. 如需詳細資訊,請參閱下一節延遲物件的例外狀況For more information, see the next section, Exceptions in Lazy Objects.

下列範例示範,相同的 Lazy<int> 執行個體在三個不同的執行緒有相同的值。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.

如果每個執行緒需要個別的資料,請使用 ThreadLocal<T> 類型,如本主題稍後所述。If you require separate data on each thread, use the ThreadLocal<T> type, as described later in this topic.

某些 Lazy<T> 建構函式有名為 isThreadSafe 的布林參數,用來指定是否從多個執行緒存取 Value 屬性。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. 如果您只打算從一個執行緒存取屬性,請傳入 false 以取得適度的效能優勢。If you intend to access the property from just one thread, pass in false to obtain a modest performance benefit. 如果打算從多個執行緒存取屬性,請傳入 true 指示 Lazy<T> 執行個體正確處理一個執行緒在初始化階段擲出例外狀況的競爭條件。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.

某些 Lazy<T> 建構函式有名為 modeLazyThreadSafetyMode 參數。Some Lazy<T> constructors have a LazyThreadSafetyMode parameter named mode. 這些建構函式提供其他的執行緒安全性模式。These constructors provide an additional thread safety mode. 下表顯示指定執行緒安全性的建構函式參數如何影響 Lazy<T> 物件的執行緒安全性。The following table shows how the thread safety of a Lazy<T> object is affected by constructor parameters that specify thread safety. 每個建構函式最多只有一個這類參數。Each constructor has at most one such parameter.

物件的執行緒安全性Thread safety of the object LazyThreadSafetyMode``mode參數LazyThreadSafetyMode mode parameter 布林 isThreadSafe 參數Boolean isThreadSafe parameter 沒有執行緒安全性參數No thread safety parameters
完整的安全執行緒,一次只有一個執行緒嘗試初始化值。Fully thread-safe; only one thread at a time tries to initialize the value. ExecutionAndPublication true 是。Yes.
不具備安全執行緒。Not thread-safe. None false 不適用。Not applicable.
完整的安全執行緒,多個執行緒競相初始化值。Fully thread-safe; threads race to initialize the value. PublicationOnly 不適用。Not applicable. 不適用。Not applicable.

如本表所示,指定 mode 參數的 LazyThreadSafetyMode.ExecutionAndPublication 如同指定 isThreadSafe 參數的 true,而指定 LazyThreadSafetyMode.None 如同指定 falseAs 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.

指定 LazyThreadSafetyMode.PublicationOnly 可讓多個執行緒嘗試初始化 Lazy<T> 執行個體。Specifying LazyThreadSafetyMode.PublicationOnly allows multiple threads to attempt to initialize the Lazy<T> instance. 只有一個執行緒可贏得這場競賽,所有其他的執行緒都會收到成功的執行緒所初始化的值。Only one thread can win this race, and all the other threads receive the value that was initialized by the successful thread. 如果在初始化期間,對執行緒擲回例外狀況,該執行緒就不會收到成功的執行緒所設定的值。If an exception is thrown on a thread during initialization, that thread does not receive the value set by the successful thread. 例外狀況不會被快取,所以後續嘗試存取 Value 屬性會導致成功初始化。Exceptions are not cached, so a subsequent attempt to access the Value property can result in successful initialization. 這不同於其他方法處理例外狀況的方式,下節中會對此加以說明。This differs from the way exceptions are treated in other modes, which is described in the following section. 如需詳細資訊,請參閱 LazyThreadSafetyMode 列舉。For more information, see the LazyThreadSafetyMode enumeration.

延遲物件的例外狀況Exceptions in Lazy Objects

如前所述,Lazy<T> 物件一律會傳回隨之初始化的相同物件或值,因此 Value 屬性是唯讀的。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. 如果啟用例外狀況快取,此不變性也會延伸至例外狀況行為。If you enable exception caching, this immutability also extends to exception behavior. 如果延遲初始化的物件啟用了異常緩存,並在首次訪問Value該屬性時從其初始化方法引發異常,則每次後續嘗試訪問該Value屬性時都會引發相同的異常。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. 換句話說,即使在多執行緒案例中,也絕對不會重新叫用包裝類型的建構函式。In other words, the constructor of the wrapped type is never re-invoked, even in multithreaded scenarios. 因此,Lazy<T> 物件無法在某次存取中擲回例外狀況,並在後續存取中傳回值。Therefore, the Lazy<T> object cannot throw an exception on one access and return a value on a subsequent access.

當您使用任何採用初始設定方法 (valueFactory 參數) 的 System.Lazy<T> 建構函式時,就啟用了例外狀況快取;例如,它會在您使用 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. 如果建構函式也採用 LazyThreadSafetyMode 值 (mode 參數),請指定 LazyThreadSafetyMode.ExecutionAndPublicationLazyThreadSafetyMode.NoneIf the constructor also takes a LazyThreadSafetyMode value (mode parameter), specify LazyThreadSafetyMode.ExecutionAndPublication or LazyThreadSafetyMode.None. 指定初始設定方法,可啟用這兩種模式的例外狀況快取。Specifying an initialization method enables exception caching for these two modes. 初始設定方法可以非常簡單。The initialization method can be very simple. 例如,它可能調用無參數建構函式的Tnew Lazy<Contents>(() => new Contents(), mode)在 C# 中New Lazy(Of Contents)(Function() New Contents()),或在 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. 如果您使用未指定初始設定方法的 System.Lazy<T> 建構函式,則不會快取 T 的無參數建構函式所擲回例外狀況。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. 如需詳細資訊,請參閱 LazyThreadSafetyMode 列舉。For more information, see the LazyThreadSafetyMode enumeration.

注意

如果您建立的 Lazy<T> 物件是將 isThreadSafe 建構函式參數設定為 false,或將 mode 建構函式參數設定為 LazyThreadSafetyMode.None,您即必須從單一執行緒存取 Lazy<T> 物件,或提供您自己的同步處理。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. 這適用該物件的所有層面,包括例外狀況快取。This applies to all aspects of the object, including exception caching.

如前一節中所述,透過指定 LazyThreadSafetyMode.PublicationOnly 所建立的 Lazy<T> 物件,會以不同的方式處理例外狀況。As noted in the previous section, Lazy<T> objects created by specifying LazyThreadSafetyMode.PublicationOnly treat exceptions differently. 有了 PublicationOnly,多個執行緒可爭奪初始化 Lazy<T> 執行個體。With PublicationOnly, multiple threads can compete to initialize the Lazy<T> instance. 本例中未快取例外狀況,但會繼續嘗試存取 Value 屬性,直到成功初始化。In this case, exceptions are not cached, and attempts to access the Value property can continue until initialization is successful.

下表摘要說明 Lazy<T> 建構函式控制例外狀況快取的方式。The following table summarizes the way the Lazy<T> constructors control exception caching.

建構函式Constructor 執行緒安全模式Thread safety mode 使用初始設定方法Uses initialization method 已快取例外狀況Exceptions are cached
Lazy(T)()Lazy(T)() (ExecutionAndPublication)(ExecutionAndPublication) No No
Lazy(T)(Func(T))Lazy(T)(Func(T)) (ExecutionAndPublication)(ExecutionAndPublication) Yes Yes
Lazy(T)(Boolean)Lazy(T)(Boolean) True (ExecutionAndPublication) 或 false (None)True (ExecutionAndPublication) or false (None) No No
Lazy(T)(Func(T), Boolean)Lazy(T)(Func(T), Boolean) True (ExecutionAndPublication) 或 false (None)True (ExecutionAndPublication) or false (None) Yes Yes
Lazy(T)(LazyThreadSafetyMode)Lazy(T)(LazyThreadSafetyMode) 使用者指定的User-specified No No
Lazy(T)(Func(T), LazyThreadSafetyMode)Lazy(T)(Func(T), LazyThreadSafetyMode) 使用者指定的User-specified Yes 如果使用者指定了 PublicationOnly 則為否;否則為是。No if user specifies PublicationOnly; otherwise, yes.

實作延遲初始化的屬性Implementing a Lazy-Initialized Property

若要使用延遲初始設定來實作公用屬性,請將屬性的支援欄位定義為 Lazy<T>,並從屬性的 get 存取子傳回 Value 屬性。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

Value 屬性是唯讀的;因此,公開它的屬性沒有任何 set 存取子。The Value property is read-only; therefore, the property that exposes it has no set accessor. 如果您需要 Lazy<T> 物件支援的讀取/寫入屬性,set 存取子必須建立新的 Lazy<T> 物件,並指派到備份存放區。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. set 存取子必須建立 Lambda 運算式,傳回已傳遞給 set 存取子的新屬性值,並將該 Lambda 運算式傳遞至新 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. 下一次存取 Value 屬性會初始化新的 Lazy<T>,且其 Value 屬性之後也會傳回已指派給屬性的新值。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. 之所以如此迂迴排列,是為了保留內建在 Lazy<T> 的多執行緒保護。The reason for this convoluted arrangement is to preserve the multithreading protections built into Lazy<T>. 否則,屬性存取子就必須快取 Value 屬性傳回的第一個值,且只能修改快取的值,而您則必須撰寫自己的執行緒安全程式碼以完成此作業。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. 因為 Lazy<T> 物件支援的讀取/寫入屬性需要額外的初始設定,效能可能不太令人滿意。Because of the additional initializations required by a read/write property backed by a Lazy<T> object, the performance might not be acceptable. 此外,根據特定的案例,可能需要其他協調以避免 setter 與 getter 之間的競爭條件。Furthermore, depending on the specific scenario, additional coordination might be required to avoid race conditions between setters and getters.

執行緒區域延遲初始設定Thread-Local Lazy Initialization

在某些多執行緒的情況下,您可能想要向各執行緒提供它專用的私用資料。In some multithreaded scenarios, you might want to give each thread its own private data. 這類資料稱為「執行緒區域資料」**。Such data is called thread-local data. 在 .NET Framework 3.5 版或更舊的版本中,您可以將 ThreadStatic 屬性套用到靜態變數,使其成為執行緒區域變數。In the .NET Framework version 3.5 and earlier, you could apply the ThreadStatic attribute to a static variable to make it thread-local. 不過,使用 ThreadStatic 屬性可能會導致極其細小的錯誤。However, using the ThreadStatic attribute can lead to subtle errors. 例如,即使基本的初始化陳述式,也只會在第一個存取它的執行緒上初始化變數,如下列範例所示。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

在所有其他的執行緒上,變數都是使用其預設值 (零) 來初始化。On all other threads, the variable will be initialized by using its default value (zero). 在 .NET Framework 第 4 版中它是替代方案,您可以使用 System.Threading.ThreadLocal<T> 類型建立執行個體式的執行緒本機變數,由您提供的 Action<T> 委派在所有執行緒上初始化。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. 在下列範例中,存取 counter 的所有執行緒都會看到其起始值為 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> 包裝其物件的方法,和 Lazy<T> 極其相似,但有以下基本差異:ThreadLocal<T> wraps its object in much the same way as Lazy<T>, with these essential differences:

  • 每個執行緒都是使用不能從其他執行緒存取的專用私用資料,來初始化執行緒區域變數。Each thread initializes the thread-local variable by using its own private data that is not accessible from other threads.

  • ThreadLocal<T>.Value 屬性是讀寫的,不限修改次數。The ThreadLocal<T>.Value property is read-write, and can be modified any number of times. 這會影響例外狀況傳播,例如,一個 get 作業可能會引發例外狀況,但下一個卻可以成功初始化值。This can affect exception propagation, for example, one get operation can raise an exception but the next one can successfully initialize the value.

  • 如未提供任何初始設定委派,則 ThreadLocal<T> 會使用預設的類型值初始化其包裝類型。If no initialization delegate is provided, ThreadLocal<T> will initialize its wrapped type by using the default value of the type. 在這方面,ThreadLocal<T>ThreadStaticAttribute 屬性一致。In this regard, ThreadLocal<T> is consistent with the ThreadStaticAttribute attribute.

下列範例示範,存取 ThreadLocal<int> 執行個體的每一個執行緒都會取得自己唯一的資料複本。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 

Parallel.For 和 ForEach 的執行緒區域變數Thread-Local Variables in Parallel.For and ForEach

當您使用 Parallel.For 方法或 Parallel.ForEach 方法平行逐一查看資料來源時,您可以使用有執行緒區域資料內建支援的多載。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. 在這些方法中,執行緒位置是使用本機委派建立、存取和清除資料所達成的。In these methods, the thread-locality is achieved by using local delegates to create, access, and clean up the data. 如需詳細資訊,請參閱如何:撰寫含有執行緒區域變數的 Parallel.For 迴圈如何:撰寫含有分割區域變數的 Parallel.ForEach 迴圈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.

低負荷情況下使用延遲初始設定Using Lazy Initialization for Low-Overhead Scenarios

在必須延遲初始化大量物件的情況下,您可能會判定,包裝 Lazy<T> 中的每個物件需要太多記憶體或太多的運算資源。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. 或者,您可能對延遲初始設定的公開方式有嚴格的要求。Or, you might have stringent requirements about how lazy initialization is exposed. 在這種情況下,您可以使用 System.Threading.LazyInitializer 類別的 static (Visual Basic 為 Shared) 方法,延遲初始化每個物件,不將它包裝在 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>.

在下列範例中,假設不將整個 Orders 物件包裝在一個 Lazy<T> 物件中,您只有在必要時才會延遲初始化個別的 Order 物件。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

請注意,本例是在每次反覆迴圈時叫用初始化程序。In this example, notice that the initialization procedure is invoked on every iteration of the loop. 在多執行緒案例中,第一個叫用初始化程序的執行緒,其值會被所有執行緒看到。In multi-threaded scenarios, the first thread to invoke the initialization procedure is the one whose value is seen by all threads. 後續其他執行緒也會叫用初始化程序,但其結果不被採用。Later threads also invoke the initialization procedure, but their results are not used. 如果無法接受這種潛在的競爭條件,請使用採用布林值引數和同步處理物件的 LazyInitializer.EnsureInitialized 多載。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.

另請參閱See also