いろいろ備忘録日記

主に .NET と Python絡みのメモを公開しています。

.NET クラスライブラリ探訪-060 (System.LazyInitializer, System.Lazy, 遅延初期化, EnsureInitialized)


今回は、System.LazyInitializerクラスについてちょこっとメモメモ。


LazyInitializerクラスは.NET Framework 4.0から追加された型です。
Lazyクラスと同様に、遅延初期化をサポートするためのクラスです。


System.Lazyについては、前回の記事をご参照ください。


LazyInitilizerクラスは、EnsureInitializedというstaticメソッドのみが公開されており
前回記述したLazyの記述を簡便化するために利用できます。


前回の記事にて記述しましたが
Lazyの場合は、以下のような記述となります。

class Hoge
{
  Lazy<HeavyObject> _heavy = new Lazy<HeavyObject>(() => new HeavyObject(), LazyThreadSafetyMode.PublicationOnly);
  
  public HeavyObject Heavy
  {
    get
    {
      return _heavy.Value;
    }
  }
}


LazyInitializerを利用すると、以下のように記述できます。

class Hoge
{
  HeavyObject _heavy;
  
  public HeavyObject Heavy
  {
    get
    {
      LazyInitializer.EnsureInitialize(ref _heavy, () => new HeavyObject());
      return _heavy;
    }
  }
}


Lazyの場合は、フィールドをLazyで宣言し、プロパティにて
そのValueを返すようにしていましたが、EnsureInitializedを利用した場合だと
フィールド宣言は、そのままの状態で、値を返す前に初期化するようになっています。
すっきりした感じになりますね。


EnsureInitializedは、LazyThreadSafetyMode.PublicationOnlyを指定した場合と
同様の動作となります。つまり、複数のスレッドが同時にアクセスした場合
オブジェクトの初期化処理は、複数行われますが最初に初期化に成功した値が
設定されます。(race-to-initializeパターン)


以下サンプルです。

  #region LazyInitializerSamples-01
  public class LazyInitializerSamples01 : IExecutable
  {
    public void Execute()
    {
      //
      // LazyInitializerは、Lazyと同様に遅延初期化を行うための
      // クラスである。このクラスは、staticメソッドのみで構成され
      // Lazyでの記述を簡便化するために存在する。
      //
      // EnsureInitializedメソッドは
      // Lazyクラスにて、LazyThreadSafetyMode.PublicationOnlyを
      // 指定した場合と同じ動作となる。(race-to-initialize)
      //
      var hasHeavy = new HasHeavyData();
      
      Parallel.Invoke
      (
        () => 
        {
          Console.WriteLine("Created. [{0}]", hasHeavy.Heavy.CreatedThreadId);
        },
        () => 
        {
          Console.WriteLine("Created. [{0}]", hasHeavy.Heavy.CreatedThreadId);
        },
        // 少し待機してから、作成済みの値にアクセス.
        () =>
        {
          Thread.Sleep(TimeSpan.FromMilliseconds(2000));
          Console.WriteLine(">>少し待機してから、作成済みの値にアクセス.");
          Console.WriteLine(">>Created. [{0}]", hasHeavy.Heavy.CreatedThreadId);
        }
      );
    }
    
    class HasHeavyData
    {
      HeavyObject _heavy;
      
      public HeavyObject Heavy
      {
        get
        {
          //
          // LazyInitializerを利用して、遅延初期化.
          //
          Console.WriteLine("[ThreadId {0}] 値初期化処理開始. start", Thread.CurrentThread.ManagedThreadId);
          LazyInitializer.EnsureInitialized(ref _heavy, () => new HeavyObject(TimeSpan.FromMilliseconds(100)));
          Console.WriteLine("[ThreadId {0}] 値初期化処理開始. end", Thread.CurrentThread.ManagedThreadId);
          
          return _heavy;
        }
      }
    }
    
    class HeavyObject
    {
      int _threadId;
      
      public HeavyObject(TimeSpan waitSpan)
      {
        Console.WriteLine(">>>>>> HeavyObjectのコンストラクタ start. [{0}]", Thread.CurrentThread.ManagedThreadId);
        Initialize(waitSpan);
        Console.WriteLine(">>>>>> HeavyObjectのコンストラクタ end.   [{0}]", Thread.CurrentThread.ManagedThreadId);
      }
      
      void Initialize(TimeSpan waitSpan)
      {
        Thread.Sleep(waitSpan);
        _threadId = Thread.CurrentThread.ManagedThreadId;
      }
      
      public int CreatedThreadId
      {
        get
        {
          return _threadId;
        }
      }
    }
  }
  #endregion


以下、実行結果です。

  [ThreadId 4] 値初期化処理開始. start
  >>>>>> HeavyObjectのコンストラクタ start. [4]
  [ThreadId 3] 値初期化処理開始. start
  >>>>>> HeavyObjectのコンストラクタ start. [3]
  >>>>>> HeavyObjectのコンストラクタ end.   [4]
  [ThreadId 4] 値初期化処理開始. end
  Created. [4]
  >>>>>> HeavyObjectのコンストラクタ end.   [3]
  [ThreadId 3] 値初期化処理開始. end
  Created. [4]
  >>少し待機してから、作成済みの値にアクセス.
  [ThreadId 4] 値初期化処理開始. start
  [ThreadId 4] 値初期化処理開始. end
  >>Created. [4]

いったんどちらのスレッド(スレッドIDが3と4)も初期化に入ってきているのですが
最初に初期化に成功したスレッドの値が適用されている事がわかります。
少し後(サンプルでは2000ミリ秒ほど待機後)に、再度値を取得していますが
この段階では、遅延初期化がすんだ後なので、設定済みの値がそのまま返ってきています。


以下、参考資料です。

================================
過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。