遅延初期化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. ユーザーが Orders データを表示しないのであれば、あるいは Orders データを計算に使用しないのであれば、システム メモリや計算処理周期を利用してそのデータを作成する理由がありません。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> コンストラクターにデリゲートが渡されない場合、値プロパティの最初のアクセスで 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 が初めてアクセスされるまで Orders はインスタンス化されません。The 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> インスタンスで、3 つの別個のスレッドに対して同じ値が与えられています。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. 1 つだけのスレッドからこのプロパティにアクセスする場合、適度なパフォーマンス上の利点が得られるように、false を渡します。If you intend to access the property from just one thread, pass in false to obtain a modest performance benefit. 複数のスレッドからプロパティにアクセスする場合、true を渡し、初期化時、1 つのスレッドが例外をスローするような競合状態を適切に処理するように 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> の一部のコンストラクターには、mode という名前の LazyThreadSafetyMode パラメーターが含まれています。Some Lazy<T> constructors have a LazyThreadSafetyMode parameter named mode. このコンストラクターでは、スレッド セーフ モードが 1 つ増えます。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. 各コンストラクターに含まれるこのようなパラメーターは多くても 1 つです。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
完全スレッドセーフ。一度に 1 つだけのスレッドが値の初期化を試行します。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 を指定することは 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.

LazyThreadSafetyMode.PublicationOnly を指定すると、Lazy<T> インスタンスの初期化を複数のスレッドが試行できます。Specifying LazyThreadSafetyMode.PublicationOnly allows multiple threads to attempt to initialize the Lazy<T> instance. 1 つだけのスレッドがこの競合を征します。他のスレッドはすべて、競合を征したスレッドにより初期化された値を受け取ります。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.ExecutionAndPublication または LazyThreadSafetyMode.None を指定します。If the constructor also takes a LazyThreadSafetyMode value (mode parameter), specify LazyThreadSafetyMode.ExecutionAndPublication or LazyThreadSafetyMode.None. 初期化メソッドを指定すると、この 2 つのモードで例外キャッシュが有効になります。Specifying an initialization method enables exception caching for these two modes. 初期化メソッドは非常に単純にすることができます。The initialization method can be very simple. たとえば、のT場合は、のnew 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.

注意

isThreadSafe コンストラクター パラメーターを false に設定するか、mode コンストラクター パラメーターを LazyThreadSafetyMode.None に設定して Lazy<T> オブジェクトを作成した場合、1 つのスレッドから 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.

前のセクションで説明したように、Lazy<T> オブジェクトの作成に LazyThreadSafetyMode.PublicationOnly を指定した場合、例外の処理が異なります。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 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

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 アクセサーは、set アクセサーに渡された新しいプロパティ値を返すラムダ式を作成し、そのラムダ式を新しい 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. さらに、シナリオによっては、セッターとゲッターの競合状態を回避するために、追加の調整が必要になる場合があります。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>.

次の例では、1 つの Lazy<T> オブジェクトで Orders オブジェクト全体をラッピングする代わりに、必要な場合にのみ、個々の 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